summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 10:34:06 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 10:34:06 +0000
commit859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch)
treed7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /spec/frontend
parent446d496a6d000c73a304be52587cd9bbc7493136 (diff)
downloadgitlab-ce-859a6fb938bb9ee2a317c46dfa4fcc1af49608f0.tar.gz
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__helpers__/emoji.js36
-rw-r--r--spec/frontend/__helpers__/fake_date/fake_date.js (renamed from spec/frontend/__helpers__/fake_date.js)21
-rw-r--r--spec/frontend/__helpers__/fake_date/fake_date_spec.js (renamed from spec/frontend/__helpers__/fake_date_spec.js)8
-rw-r--r--spec/frontend/__helpers__/fake_date/index.js2
-rw-r--r--spec/frontend/__helpers__/fake_date/jest.js41
-rw-r--r--spec/frontend/__helpers__/graphql_helpers.js14
-rw-r--r--spec/frontend/__helpers__/graphql_helpers_spec.js23
-rw-r--r--spec/frontend/__helpers__/init_vue_mr_page_helper.js4
-rw-r--r--spec/frontend/__helpers__/jest_execution_watcher.js12
-rw-r--r--spec/frontend/__helpers__/stub_component.js25
-rw-r--r--spec/frontend/__mocks__/@gitlab/ui.js4
-rw-r--r--spec/frontend/access_tokens/components/expires_at_field_spec.js3
-rw-r--r--spec/frontend/actioncable_connection_monitor_spec.js79
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js8
-rw-r--r--spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js2
-rw-r--r--spec/frontend/add_context_commits_modal/store/actions_spec.js2
-rw-r--r--spec/frontend/add_context_commits_modal/store/mutations_spec.js2
-rw-r--r--spec/frontend/admin/statistics_panel/components/app_spec.js8
-rw-r--r--spec/frontend/admin/statistics_panel/store/actions_spec.js2
-rw-r--r--spec/frontend/admin/statistics_panel/store/getters_spec.js2
-rw-r--r--spec/frontend/admin/statistics_panel/store/mutations_spec.js2
-rw-r--r--spec/frontend/admin/users/components/actions/actions_spec.js98
-rw-r--r--spec/frontend/admin/users/components/user_actions_spec.js158
-rw-r--r--spec/frontend/admin/users/components/user_avatar_spec.js85
-rw-r--r--spec/frontend/admin/users/components/user_date_spec.js34
-rw-r--r--spec/frontend/admin/users/components/users_table_spec.js28
-rw-r--r--spec/frontend/admin/users/constants.js19
-rw-r--r--spec/frontend/admin/users/index_spec.js4
-rw-r--r--spec/frontend/admin/users/mock_data.js1
-rw-r--r--spec/frontend/alert_management/components/alert_management_empty_state_spec.js2
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js2
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js8
-rw-r--r--spec/frontend/alerts_service_settings/components/__snapshots__/alerts_service_form_spec.js.snap9
-rw-r--r--spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js152
-rw-r--r--spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap98
-rw-r--r--spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap406
-rw-r--r--spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js (renamed from spec/frontend/alerts_settings/alert_mapping_builder_spec.js)49
-rw-r--r--spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js (renamed from spec/frontend/alerts_settings/alerts_integrations_list_spec.js)2
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_form_spec.js (renamed from spec/frontend/alerts_settings/alerts_settings_form_spec.js)242
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js (renamed from spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js)24
-rw-r--r--spec/frontend/alerts_settings/components/mocks/apollo_mock.js (renamed from spec/frontend/alerts_settings/mocks/apollo_mock.js)0
-rw-r--r--spec/frontend/alerts_settings/components/mocks/integrations.json (renamed from spec/frontend/alerts_settings/mocks/integrations.json)0
-rw-r--r--spec/frontend/alerts_settings/components/util.js (renamed from spec/frontend/alerts_settings/util.js)0
-rw-r--r--spec/frontend/alerts_settings/mocks/alertFields.json123
-rw-r--r--spec/frontend/alerts_settings/utils/mapping_transformations_spec.js81
-rw-r--r--spec/frontend/analytics/components/activity_chart_spec.js2
-rw-r--r--spec/frontend/analytics/instance_statistics/components/app_spec.js4
-rw-r--r--spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js6
-rw-r--r--spec/frontend/analytics/instance_statistics/components/projects_and_groups_chart_spec.js15
-rw-r--r--spec/frontend/analytics/instance_statistics/components/users_chart_spec.js13
-rw-r--r--spec/frontend/analytics/shared/components/metric_card_spec.js2
-rw-r--r--spec/frontend/api/api_utils_spec.js4
-rw-r--r--spec/frontend/api_spec.js24
-rw-r--r--spec/frontend/artifacts_settings/components/__snapshots__/keep_latest_artifact_checkbox_spec.js.snap55
-rw-r--r--spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js96
-rw-r--r--spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js8
-rw-r--r--spec/frontend/authentication/two_factor_auth/index_spec.js4
-rw-r--r--spec/frontend/avatar_helper_spec.js2
-rw-r--r--spec/frontend/awards_handler_spec.js20
-rw-r--r--spec/frontend/badges/components/badge_form_spec.js10
-rw-r--r--spec/frontend/badges/components/badge_list_row_spec.js2
-rw-r--r--spec/frontend/badges/components/badge_list_spec.js2
-rw-r--r--spec/frontend/badges/components/badge_settings_spec.js8
-rw-r--r--spec/frontend/badges/store/actions_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js23
-rw-r--r--spec/frontend/batch_comments/components/preview_item_spec.js19
-rw-r--r--spec/frontend/batch_comments/components/publish_dropdown_spec.js4
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js2
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/mutations_spec.js4
-rw-r--r--spec/frontend/behaviors/autosize_spec.js32
-rw-r--r--spec/frontend/behaviors/copy_as_gfm_spec.js2
-rw-r--r--spec/frontend/behaviors/gl_emoji_spec.js4
-rw-r--r--spec/frontend/blob/balsamiq/balsamiq_viewer_spec.js2
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap2
-rw-r--r--spec/frontend/blob/components/blob_content_error_spec.js2
-rw-r--r--spec/frontend/blob/components/blob_content_spec.js4
-rw-r--r--spec/frontend/blob/components/blob_edit_header_spec.js2
-rw-r--r--spec/frontend/blob/components/blob_header_default_actions_spec.js2
-rw-r--r--spec/frontend/blob/components/blob_header_filepath_spec.js2
-rw-r--r--spec/frontend/blob/components/blob_header_spec.js2
-rw-r--r--spec/frontend/blob/components/blob_header_viewer_switcher_spec.js2
-rw-r--r--spec/frontend/blob/notebook/notebook_viever_spec.js4
-rw-r--r--spec/frontend/blob/pdf/pdf_viewer_spec.js2
-rw-r--r--spec/frontend/blob/pipeline_tour_success_modal_spec.js4
-rw-r--r--spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js2
-rw-r--r--spec/frontend/blob/utils_spec.js2
-rw-r--r--spec/frontend/blob/viewer/index_spec.js2
-rw-r--r--spec/frontend/blob_edit/edit_blob_spec.js2
-rw-r--r--spec/frontend/boards/board_list_deprecated_spec.js11
-rw-r--r--spec/frontend/boards/board_list_helper.js8
-rw-r--r--spec/frontend/boards/board_list_spec.js8
-rw-r--r--spec/frontend/boards/board_new_issue_deprecated_spec.js4
-rw-r--r--spec/frontend/boards/boards_store_spec.js6
-rw-r--r--spec/frontend/boards/boards_util_spec.js17
-rw-r--r--spec/frontend/boards/components/board_assignee_dropdown_spec.js380
-rw-r--r--spec/frontend/boards/components/board_card_layout_deprecated_spec.js158
-rw-r--r--spec/frontend/boards/components/board_card_layout_spec.js67
-rw-r--r--spec/frontend/boards/components/board_card_spec.js10
-rw-r--r--spec/frontend/boards/components/board_column_deprecated_spec.js16
-rw-r--r--spec/frontend/boards/components/board_column_spec.js13
-rw-r--r--spec/frontend/boards/components/board_configuration_options_spec.js15
-rw-r--r--spec/frontend/boards/components/board_content_spec.js6
-rw-r--r--spec/frontend/boards/components/board_form_spec.js55
-rw-r--r--spec/frontend/boards/components/board_list_header_deprecated_spec.js12
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js12
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js2
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js10
-rw-r--r--spec/frontend/boards/components/boards_selector_deprecated_spec.js214
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js30
-rw-r--r--spec/frontend/boards/components/issue_time_estimate_spec.js2
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js2
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js4
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js6
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js6
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js50
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js8
-rw-r--r--spec/frontend/boards/components/sidebar/remove_issue_spec.js2
-rw-r--r--spec/frontend/boards/issue_card_deprecated_spec.js4
-rw-r--r--spec/frontend/boards/issue_card_inner_spec.js6
-rw-r--r--spec/frontend/boards/issue_spec.js2
-rw-r--r--spec/frontend/boards/list_spec.js3
-rw-r--r--spec/frontend/boards/mock_data.js6
-rw-r--r--spec/frontend/boards/project_select_deprecated_spec.js9
-rw-r--r--spec/frontend/boards/project_select_spec.js7
-rw-r--r--spec/frontend/boards/stores/actions_spec.js219
-rw-r--r--spec/frontend/boards/stores/getters_spec.js20
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js44
-rw-r--r--spec/frontend/branches/divergence_graph_spec.js2
-rw-r--r--spec/frontend/captcha/captcha_modal_spec.js171
-rw-r--r--spec/frontend/captcha/init_recaptcha_script_spec.js59
-rw-r--r--spec/frontend/ci_lint/components/ci_lint_spec.js2
-rw-r--r--spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js4
-rw-r--r--spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js4
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js6
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js2
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js2
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_table_spec.js4
-rw-r--r--spec/frontend/ci_variable_list/store/actions_spec.js8
-rw-r--r--spec/frontend/ci_variable_list/store/mutations_spec.js4
-rw-r--r--spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap8
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js6
-rw-r--r--spec/frontend/clusters/components/applications_spec.js68
-rw-r--r--spec/frontend/clusters/components/fluentd_output_settings_spec.js2
-rw-r--r--spec/frontend/clusters/components/ingress_modsecurity_settings_spec.js2
-rw-r--r--spec/frontend/clusters/components/knative_domain_editor_spec.js2
-rw-r--r--spec/frontend/clusters/components/new_cluster_spec.js2
-rw-r--r--spec/frontend/clusters/components/remove_cluster_confirmation_spec.js4
-rw-r--r--spec/frontend/clusters/components/uninstall_application_button_spec.js2
-rw-r--r--spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js2
-rw-r--r--spec/frontend/clusters/components/update_application_confirmation_modal_spec.js2
-rw-r--r--spec/frontend/clusters/forms/components/integration_form_spec.js4
-rw-r--r--spec/frontend/clusters/services/application_state_machine_spec.js2
-rw-r--r--spec/frontend/clusters/services/crossplane_provider_stack_spec.js2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js8
-rw-r--r--spec/frontend/clusters_list/components/node_error_help_text_spec.js2
-rw-r--r--spec/frontend/clusters_list/store/actions_spec.js10
-rw-r--r--spec/frontend/clusters_list/store/mutations_spec.js4
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js4
-rw-r--r--spec/frontend/code_navigation/components/popover_spec.js2
-rw-r--r--spec/frontend/code_navigation/store/actions_spec.js2
-rw-r--r--spec/frontend/collapsed_sidebar_todo_spec.js16
-rw-r--r--spec/frontend/commit/commit_pipeline_status_component_spec.js10
-rw-r--r--spec/frontend/commit/pipelines/pipelines_spec.js4
-rw-r--r--spec/frontend/commits_spec.js4
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js2
-rw-r--r--spec/frontend/confidential_merge_request/components/dropdown_spec.js2
-rw-r--r--spec/frontend/confidential_merge_request/components/project_form_group_spec.js2
-rw-r--r--spec/frontend/contributors/component/contributors_spec.js4
-rw-r--r--spec/frontend/contributors/store/actions_spec.js4
-rw-r--r--spec/frontend/contributors/store/mutations_spec.js4
-rw-r--r--spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js2
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js2
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js4
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js4
-rw-r--r--spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js2
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/actions_spec.js10
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js2
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js4
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js4
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js4
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js2
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js4
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js4
-rw-r--r--spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js4
-rw-r--r--spec/frontend/create_cluster/gke_cluster/stores/mutations_spec.js2
-rw-r--r--spec/frontend/create_cluster/init_create_cluster_spec.js2
-rw-r--r--spec/frontend/create_cluster/store/cluster_dropdown/actions_spec.js4
-rw-r--r--spec/frontend/create_cluster/store/cluster_dropdown/mutations_spec.js2
-rw-r--r--spec/frontend/create_merge_request_dropdown_spec.js4
-rw-r--r--spec/frontend/cycle_analytics/limit_warning_component_spec.js4
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js6
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js4
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js2
-rw-r--r--spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js6
-rw-r--r--spec/frontend/deploy_freeze/store/actions_spec.js6
-rw-r--r--spec/frontend/deploy_freeze/store/mutations_spec.js4
-rw-r--r--spec/frontend/deploy_keys/components/action_btn_spec.js4
-rw-r--r--spec/frontend/deploy_keys/components/app_spec.js6
-rw-r--r--spec/frontend/deploy_keys/components/key_spec.js4
-rw-r--r--spec/frontend/deploy_keys/components/keys_panel_spec.js2
-rw-r--r--spec/frontend/design_management/components/delete_button_spec.js2
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js6
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_overlay_spec.js2
-rw-r--r--spec/frontend/design_management/components/design_presentation_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_scaler_spec.js2
-rw-r--r--spec/frontend/design_management/components/design_sidebar_spec.js8
-rw-r--r--spec/frontend/design_management/components/design_todo_button_spec.js4
-rw-r--r--spec/frontend/design_management/components/image_spec.js2
-rw-r--r--spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap10
-rw-r--r--spec/frontend/design_management/components/list/item_spec.js52
-rw-r--r--spec/frontend/design_management/components/toolbar/index_spec.js4
-rw-r--r--spec/frontend/design_management/components/upload/button_spec.js2
-rw-r--r--spec/frontend/design_management/components/upload/design_version_dropdown_spec.js2
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js26
-rw-r--r--spec/frontend/design_management/pages/index_spec.js30
-rw-r--r--spec/frontend/design_management/router_spec.js2
-rw-r--r--spec/frontend/design_management/utils/cache_update_spec.js2
-rw-r--r--spec/frontend/design_management/utils/design_management_utils_spec.js4
-rw-r--r--spec/frontend/diffs/components/app_spec.js24
-rw-r--r--spec/frontend/diffs/components/collapsed_files_warning_spec.js4
-rw-r--r--spec/frontend/diffs/components/commit_item_spec.js2
-rw-r--r--spec/frontend/diffs/components/commit_widget_spec.js2
-rw-r--r--spec/frontend/diffs/components/compare_dropdown_layout_spec.js4
-rw-r--r--spec/frontend/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_comment_cell_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_content_spec.js14
-rw-r--r--spec/frontend/diffs/components/diff_discussions_spec.js8
-rw-r--r--spec/frontend/diffs/components/diff_expansion_cell_spec.js8
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js304
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js78
-rw-r--r--spec/frontend/diffs/components/diff_line_note_form_spec.js5
-rw-r--r--spec/frontend/diffs/components/diff_row_spec.js6
-rw-r--r--spec/frontend/diffs/components/diff_row_utils_spec.js11
-rw-r--r--spec/frontend/diffs/components/diff_stats_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_view_spec.js14
-rw-r--r--spec/frontend/diffs/components/image_diff_overlay_spec.js2
-rw-r--r--spec/frontend/diffs/components/inline_diff_table_row_spec.js8
-rw-r--r--spec/frontend/diffs/components/inline_diff_view_spec.js8
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js4
-rw-r--r--spec/frontend/diffs/components/parallel_diff_table_row_spec.js10
-rw-r--r--spec/frontend/diffs/components/parallel_diff_view_spec.js6
-rw-r--r--spec/frontend/diffs/components/settings_dropdown_spec.js2
-rw-r--r--spec/frontend/diffs/components/tree_list_spec.js2
-rw-r--r--spec/frontend/diffs/store/actions_spec.js8
-rw-r--r--spec/frontend/diffs/store/getters_spec.js68
-rw-r--r--spec/frontend/diffs/store/getters_versions_dropdowns_spec.js4
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js6
-rw-r--r--spec/frontend/diffs/store/utils_spec.js4
-rw-r--r--spec/frontend/diffs/utils/diff_file_spec.js13
-rw-r--r--spec/frontend/diffs/utils/file_reviews_spec.js48
-rw-r--r--spec/frontend/diffs/utils/preferences_spec.js5
-rw-r--r--spec/frontend/diffs/utils/suggestions_spec.js15
-rw-r--r--spec/frontend/dirty_submit/dirty_submit_factory_spec.js2
-rw-r--r--spec/frontend/droplab/drop_down_spec.js2
-rw-r--r--spec/frontend/droplab/hook_spec.js2
-rw-r--r--spec/frontend/droplab/plugins/ajax_filter_spec.js2
-rw-r--r--spec/frontend/droplab/plugins/ajax_spec.js2
-rw-r--r--spec/frontend/dropzone_input_spec.js4
-rw-r--r--spec/frontend/editor/editor_ci_schema_ext_spec.js2
-rw-r--r--spec/frontend/editor/editor_lite_spec.js298
-rw-r--r--spec/frontend/emoji/index_spec.js (renamed from spec/frontend/emoji/emoji_spec.js)361
-rw-r--r--spec/frontend/environment.js13
-rw-r--r--spec/frontend/environments/canary_ingress_spec.js4
-rw-r--r--spec/frontend/environments/canary_update_modal_spec.js2
-rw-r--r--spec/frontend/environments/confirm_rollback_modal_spec.js2
-rw-r--r--spec/frontend/environments/deploy_board_component_spec.js2
-rw-r--r--spec/frontend/environments/enable_review_app_modal_spec.js2
-rw-r--r--spec/frontend/environments/environment_actions_spec.js4
-rw-r--r--spec/frontend/environments/environment_item_spec.js4
-rw-r--r--spec/frontend/environments/environment_monitoring_spec.js2
-rw-r--r--spec/frontend/environments/environment_pin_spec.js4
-rw-r--r--spec/frontend/environments/environment_rollback_spec.js4
-rw-r--r--spec/frontend/environments/environment_stop_spec.js4
-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/error_tracking/components/error_details_spec.js14
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_actions_spec.js2
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js10
-rw-r--r--spec/frontend/error_tracking/components/stacktrace_entry_spec.js2
-rw-r--r--spec/frontend/error_tracking/store/actions_spec.js4
-rw-r--r--spec/frontend/error_tracking/store/details/actions_spec.js4
-rw-r--r--spec/frontend/error_tracking/store/list/actions_spec.js6
-rw-r--r--spec/frontend/error_tracking/store/list/mutation_spec.js2
-rw-r--r--spec/frontend/error_tracking_settings/components/app_spec.js2
-rw-r--r--spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js4
-rw-r--r--spec/frontend/error_tracking_settings/components/project_dropdown_spec.js4
-rw-r--r--spec/frontend/error_tracking_settings/store/actions_spec.js8
-rw-r--r--spec/frontend/error_tracking_settings/store/mutation_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/edit_feature_flag_spec.js19
-rw-r--r--spec/frontend/feature_flags/components/environments_dropdown_spec.js6
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_spec.js12
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_tab_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_table_spec.js4
-rw-r--r--spec/frontend/feature_flags/components/form_spec.js47
-rw-r--r--spec/frontend/feature_flags/components/new_environments_dropdown_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/new_feature_flag_spec.js16
-rw-r--r--spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js4
-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.js4
-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.js10
-rw-r--r--spec/frontend/feature_flags/components/strategy_spec.js12
-rw-r--r--spec/frontend/feature_flags/components/user_lists_table_spec.js2
-rw-r--r--spec/frontend/feature_flags/store/edit/actions_spec.js12
-rw-r--r--spec/frontend/feature_flags/store/edit/mutations_spec.js4
-rw-r--r--spec/frontend/feature_flags/store/gitlab_user_lists/actions_spec.js2
-rw-r--r--spec/frontend/feature_flags/store/gitlab_user_lists/getters_spec.js2
-rw-r--r--spec/frontend/feature_flags/store/gitlab_user_lists/mutations_spec.js4
-rw-r--r--spec/frontend/feature_flags/store/helpers_spec.js14
-rw-r--r--spec/frontend/feature_flags/store/index/actions_spec.js4
-rw-r--r--spec/frontend/feature_flags/store/index/mutations_spec.js6
-rw-r--r--spec/frontend/feature_flags/store/new/actions_spec.js16
-rw-r--r--spec/frontend/feature_flags/store/new/mutations_spec.js4
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_helper_spec.js70
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_options_spec.js22
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_popover_spec.js80
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_spec.js120
-rw-r--r--spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js2
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js3
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js12
-rw-r--r--spec/frontend/filtered_search/filtered_search_tokenizer_spec.js2
-rw-r--r--spec/frontend/filtered_search/recent_searches_root_spec.js49
-rw-r--r--spec/frontend/filtered_search/visual_token_value_spec.js4
-rw-r--r--spec/frontend/fixtures/api_merge_requests.rb19
-rw-r--r--spec/frontend/fixtures/merge_requests.rb1
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb2
-rw-r--r--spec/frontend/fixtures/pipelines.rb2
-rw-r--r--spec/frontend/frequent_items/components/app_spec.js39
-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.js2
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_search_input_spec.js62
-rw-r--r--spec/frontend/frequent_items/store/actions_spec.js6
-rw-r--r--spec/frontend/frequent_items/store/getters_spec.js2
-rw-r--r--spec/frontend/frequent_items/store/mutations_spec.js4
-rw-r--r--spec/frontend/frequent_items/utils_spec.js2
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js128
-rw-r--r--spec/frontend/gl_form_spec.js2
-rw-r--r--spec/frontend/gpg_badges_spec.js2
-rw-r--r--spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap6
-rw-r--r--spec/frontend/grafana_integration/components/grafana_integration_spec.js4
-rw-r--r--spec/frontend/group_settings/components/shared_runners_form_spec.js2
-rw-r--r--spec/frontend/groups/components/app_spec.js12
-rw-r--r--spec/frontend/groups/components/group_folder_spec.js2
-rw-r--r--spec/frontend/groups/components/group_item_spec.js6
-rw-r--r--spec/frontend/groups/components/groups_spec.js6
-rw-r--r--spec/frontend/groups/components/invite_members_banner_spec.js4
-rw-r--r--spec/frontend/groups/components/item_actions_spec.js16
-rw-r--r--spec/frontend/groups/components/item_caret_spec.js2
-rw-r--r--spec/frontend/groups/components/item_stats_value_spec.js2
-rw-r--r--spec/frontend/groups/components/item_type_icon_spec.js2
-rw-r--r--spec/frontend/groups/members/mock_data.js33
-rw-r--r--spec/frontend/groups/members/utils_spec.js45
-rw-r--r--spec/frontend/groups/service/groups_service_spec.js2
-rw-r--r--spec/frontend/ide/commit_icon_spec.js2
-rw-r--r--spec/frontend/ide/components/activity_bar_spec.js21
-rw-r--r--spec/frontend/ide/components/branches/item_spec.js6
-rw-r--r--spec/frontend/ide/components/branches/search_list_spec.js6
-rw-r--r--spec/frontend/ide/components/commit_sidebar/actions_spec.js27
-rw-r--r--spec/frontend/ide/components/commit_sidebar/editor_header_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/empty_state_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/form_spec.js395
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_item_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js11
-rw-r--r--spec/frontend/ide/components/commit_sidebar/radio_group_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/success_message_spec.js2
-rw-r--r--spec/frontend/ide/components/error_message_spec.js2
-rw-r--r--spec/frontend/ide/components/file_row_extra_spec.js2
-rw-r--r--spec/frontend/ide/components/file_templates/bar_spec.js2
-rw-r--r--spec/frontend/ide/components/file_templates/dropdown_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_file_row_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_review_spec.js6
-rw-r--r--spec/frontend/ide/components/ide_side_bar_spec.js6
-rw-r--r--spec/frontend/ide/components/ide_sidebar_nav_spec.js5
-rw-r--r--spec/frontend/ide/components/ide_spec.js60
-rw-r--r--spec/frontend/ide/components/ide_status_bar_spec.js6
-rw-r--r--spec/frontend/ide/components/ide_status_list_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_status_mr_spec.js2
-rw-r--r--spec/frontend/ide/components/ide_tree_spec.js2
-rw-r--r--spec/frontend/ide/components/jobs/detail/scroll_button_spec.js2
-rw-r--r--spec/frontend/ide/components/jobs/list_spec.js2
-rw-r--r--spec/frontend/ide/components/jobs/stage_spec.js4
-rw-r--r--spec/frontend/ide/components/merge_requests/item_spec.js6
-rw-r--r--spec/frontend/ide/components/merge_requests/list_spec.js4
-rw-r--r--spec/frontend/ide/components/nav_dropdown_spec.js4
-rw-r--r--spec/frontend/ide/components/new_dropdown/index_spec.js2
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js4
-rw-r--r--spec/frontend/ide/components/panes/collapsible_sidebar_spec.js4
-rw-r--r--spec/frontend/ide/components/panes/right_spec.js6
-rw-r--r--spec/frontend/ide/components/pipelines/list_spec.js6
-rw-r--r--spec/frontend/ide/components/preview/clientside_spec.js2
-rw-r--r--spec/frontend/ide/components/preview/navigator_spec.js4
-rw-r--r--spec/frontend/ide/components/repo_commit_section_spec.js6
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js16
-rw-r--r--spec/frontend/ide/components/repo_tab_spec.js2
-rw-r--r--spec/frontend/ide/components/repo_tabs_spec.js4
-rw-r--r--spec/frontend/ide/components/resizable_panel_spec.js4
-rw-r--r--spec/frontend/ide/components/terminal/empty_state_spec.js2
-rw-r--r--spec/frontend/ide/components/terminal/session_spec.js2
-rw-r--r--spec/frontend/ide/components/terminal/terminal_controls_spec.js2
-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.js2
-rw-r--r--spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js4
-rw-r--r--spec/frontend/ide/helpers.js2
-rw-r--r--spec/frontend/ide/lib/create_diff_spec.js2
-rw-r--r--spec/frontend/ide/lib/create_file_diff_spec.js2
-rw-r--r--spec/frontend/ide/lib/decorations/controller_spec.js6
-rw-r--r--spec/frontend/ide/lib/diff/controller_spec.js2
-rw-r--r--spec/frontend/ide/lib/editor_spec.js5
-rw-r--r--spec/frontend/ide/lib/languages/hcl_spec.js2
-rw-r--r--spec/frontend/ide/lib/languages/vue_spec.js2
-rw-r--r--spec/frontend/ide/services/index_spec.js4
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js10
-rw-r--r--spec/frontend/ide/stores/actions/merge_request_spec.js111
-rw-r--r--spec/frontend/ide/stores/actions/project_spec.js8
-rw-r--r--spec/frontend/ide/stores/actions/tree_spec.js8
-rw-r--r--spec/frontend/ide/stores/actions_spec.js8
-rw-r--r--spec/frontend/ide/stores/getters_spec.js7
-rw-r--r--spec/frontend/ide/stores/modules/branches/actions_spec.js6
-rw-r--r--spec/frontend/ide/stores/modules/branches/mutations_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/clientside/actions_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js31
-rw-r--r--spec/frontend/ide/stores/modules/commit/getters_spec.js11
-rw-r--r--spec/frontend/ide/stores/modules/commit/mutations_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/editor/actions_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/editor/getters_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/editor/mutations_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/actions_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/getters_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/mutations_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/merge_requests/actions_spec.js6
-rw-r--r--spec/frontend/ide/stores/modules/merge_requests/mutations_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/pane/mutations_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/pipelines/actions_spec.js8
-rw-r--r--spec/frontend/ide/stores/modules/pipelines/mutations_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/router/mutations_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js6
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/session_status_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/setup_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/terminal/messages_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/terminal/mutations_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/terminal_sync/actions_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/terminal_sync/mutations_spec.js2
-rw-r--r--spec/frontend/ide/stores/mutations/file_spec.js2
-rw-r--r--spec/frontend/ide/stores/plugins/terminal_spec.js2
-rw-r--r--spec/frontend/ide/stores/plugins/terminal_sync_spec.js8
-rw-r--r--spec/frontend/ide/stores/utils_spec.js2
-rw-r--r--spec/frontend/image_diff/image_badge_spec.js2
-rw-r--r--spec/frontend/image_diff/image_diff_spec.js2
-rw-r--r--spec/frontend/image_diff/init_discussion_tab_spec.js2
-rw-r--r--spec/frontend/image_diff/replaced_image_diff_spec.js4
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_row_spec.js6
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_spec.js167
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js137
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js4
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js276
-rw-r--r--spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js4
-rw-r--r--spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js14
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js12
-rw-r--r--spec/frontend/import_entities/import_projects/store/actions_spec.js12
-rw-r--r--spec/frontend/import_entities/import_projects/store/getters_spec.js2
-rw-r--r--spec/frontend/import_entities/import_projects/store/mutations_spec.js2
-rw-r--r--spec/frontend/import_entities/import_projects/utils_spec.js2
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js10
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap4
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_service_spec.js6
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js2
-rw-r--r--spec/frontend/incidents_settings/components/pagerduty_form_spec.js2
-rw-r--r--spec/frontend/integrations/edit/components/active_checkbox_spec.js5
-rw-r--r--spec/frontend/integrations/edit/components/confirmation_modal_spec.js5
-rw-r--r--spec/frontend/integrations/edit/components/dynamic_field_spec.js2
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js169
-rw-r--r--spec/frontend/integrations/edit/components/jira_issues_fields_spec.js86
-rw-r--r--spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js2
-rw-r--r--spec/frontend/integrations/edit/components/override_dropdown_spec.js7
-rw-r--r--spec/frontend/integrations/edit/components/trigger_fields_spec.js2
-rw-r--r--spec/frontend/integrations/edit/store/actions_spec.js37
-rw-r--r--spec/frontend/integrations/edit/store/getters_spec.js4
-rw-r--r--spec/frontend/integrations/edit/store/mutations_spec.js28
-rw-r--r--spec/frontend/integrations/edit/store/state_spec.js3
-rw-r--r--spec/frontend/integrations/integration_settings_form_spec.js81
-rw-r--r--spec/frontend/invite_member/components/invite_member_modal_spec.js4
-rw-r--r--spec/frontend/invite_member/components/invite_member_trigger_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js2
-rw-r--r--spec/frontend/invite_members/components/members_token_select_spec.js4
-rw-r--r--spec/frontend/issuable/components/issuable_by_email_spec.js164
-rw-r--r--spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js2
-rw-r--r--spec/frontend/issuable/related_issues/components/issue_token_spec.js2
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_block_spec.js2
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_root_spec.js6
-rw-r--r--spec/frontend/issuable_create/components/issuable_form_spec.js5
-rw-r--r--spec/frontend/issuable_list/components/issuable_item_spec.js70
-rw-r--r--spec/frontend/issuable_list/components/issuable_list_root_spec.js4
-rw-r--r--spec/frontend/issuable_list/components/issuable_tabs_spec.js2
-rw-r--r--spec/frontend/issuable_show/components/issuable_body_spec.js11
-rw-r--r--spec/frontend/issuable_show/components/issuable_description_spec.js2
-rw-r--r--spec/frontend/issuable_show/components/issuable_edit_form_spec.js4
-rw-r--r--spec/frontend/issuable_show/components/issuable_header_spec.js61
-rw-r--r--spec/frontend/issuable_show/components/issuable_show_root_spec.js4
-rw-r--r--spec/frontend/issuable_show/components/issuable_title_spec.js2
-rw-r--r--spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js2
-rw-r--r--spec/frontend/issuable_spec.js42
-rw-r--r--spec/frontend/issuable_suggestions/components/item_spec.js4
-rw-r--r--spec/frontend/issue_show/components/app_spec.js14
-rw-r--r--spec/frontend/issue_show/components/description_spec.js2
-rw-r--r--spec/frontend/issue_show/components/fields/description_spec.js78
-rw-r--r--spec/frontend/issue_show/components/fields/description_template_spec.js6
-rw-r--r--spec/frontend/issue_show/components/fields/title_spec.js48
-rw-r--r--spec/frontend/issue_show/components/form_spec.js9
-rw-r--r--spec/frontend/issue_show/components/incidents/highlight_bar_spec.js2
-rw-r--r--spec/frontend/issue_show/components/incidents/incident_tabs_spec.js12
-rw-r--r--spec/frontend/issue_show/components/pinned_links_spec.js2
-rw-r--r--spec/frontend/issue_show/components/title_spec.js2
-rw-r--r--spec/frontend/issue_show/helpers.js9
-rw-r--r--spec/frontend/issue_show/issue_spec.js4
-rw-r--r--spec/frontend/issue_show/mock_data.js1
-rw-r--r--spec/frontend/issue_spec.js4
-rw-r--r--spec/frontend/issues_list/components/issuable_spec.js37
-rw-r--r--spec/frontend/issues_list/components/issuables_list_app_spec.js84
-rw-r--r--spec/frontend/jira_connect/api_spec.js5
-rw-r--r--spec/frontend/jira_connect/components/app_spec.js36
-rw-r--r--spec/frontend/jira_connect/components/groups_list_item_spec.js109
-rw-r--r--spec/frontend/jira_connect/components/groups_list_spec.js30
-rw-r--r--spec/frontend/jira_connect/index_spec.js56
-rw-r--r--spec/frontend/jira_connect/mock_data.js2
-rw-r--r--spec/frontend/jira_import/components/jira_import_form_spec.js81
-rw-r--r--spec/frontend/jira_import/mock_data.js2
-rw-r--r--spec/frontend/jira_import/utils/jira_import_utils_spec.js2
-rw-r--r--spec/frontend/jobs/components/artifacts_block_spec.js2
-rw-r--r--spec/frontend/jobs/components/erased_block_spec.js8
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js13
-rw-r--r--spec/frontend/jobs/components/job_container_item_spec.js102
-rw-r--r--spec/frontend/jobs/components/job_sidebar_details_container_spec.js11
-rw-r--r--spec/frontend/jobs/components/job_sidebar_retry_button_spec.js2
-rw-r--r--spec/frontend/jobs/components/log/line_header_spec.js2
-rw-r--r--spec/frontend/jobs/components/log/log_spec.js2
-rw-r--r--spec/frontend/jobs/components/manual_variables_form_spec.js2
-rw-r--r--spec/frontend/jobs/components/sidebar_detail_row_spec.js72
-rw-r--r--spec/frontend/jobs/components/sidebar_spec.js6
-rw-r--r--spec/frontend/jobs/components/trigger_block_spec.js116
-rw-r--r--spec/frontend/jobs/components/unmet_prerequisites_block_spec.js2
-rw-r--r--spec/frontend/jobs/mixins/delayed_job_mixin_spec.js98
-rw-r--r--spec/frontend/jobs/store/actions_spec.js6
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js4
-rw-r--r--spec/frontend/lazy_loader_spec.js2
-rw-r--r--spec/frontend/lib/utils/ajax_cache_spec.js2
-rw-r--r--spec/frontend/lib/utils/array_utility_spec.js32
-rw-r--r--spec/frontend/lib/utils/color_utils_spec.js17
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js8
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js345
-rw-r--r--spec/frontend/lib/utils/poll_spec.js2
-rw-r--r--spec/frontend/lib/utils/poll_until_complete_spec.js2
-rw-r--r--spec/frontend/lib/utils/unit_format/formatter_factory_spec.js21
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js55
-rw-r--r--spec/frontend/line_highlighter_spec.js2
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js2
-rw-r--r--spec/frontend/logs/components/log_advanced_filters_spec.js9
-rw-r--r--spec/frontend/logs/components/log_control_buttons_spec.js2
-rw-r--r--spec/frontend/logs/components/log_simple_filters_spec.js3
-rw-r--r--spec/frontend/logs/stores/actions_spec.js13
-rw-r--r--spec/frontend/logs/stores/mutations_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/leave_button_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/resend_invite_button_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/user_action_buttons_spec.js4
-rw-r--r--spec/frontend/members/components/app_spec.js (renamed from spec/frontend/groups/members/components/app_spec.js)12
-rw-r--r--spec/frontend/members/components/avatars/group_avatar_spec.js6
-rw-r--r--spec/frontend/members/components/avatars/invite_avatar_spec.js4
-rw-r--r--spec/frontend/members/components/avatars/user_avatar_spec.js6
-rw-r--r--spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js2
-rw-r--r--spec/frontend/members/components/filter_sort/sort_dropdown_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.js4
-rw-r--r--spec/frontend/members/components/table/created_at_spec.js2
-rw-r--r--spec/frontend/members/components/table/expiration_datepicker_spec.js4
-rw-r--r--spec/frontend/members/components/table/expires_at_spec.js2
-rw-r--r--spec/frontend/members/components/table/member_action_buttons_spec.js10
-rw-r--r--spec/frontend/members/components/table/member_avatar_spec.js8
-rw-r--r--spec/frontend/members/components/table/member_source_spec.js2
-rw-r--r--spec/frontend/members/components/table/members_table_cell_spec.js24
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js25
-rw-r--r--spec/frontend/members/components/table/role_dropdown_spec.js11
-rw-r--r--spec/frontend/members/index_spec.js (renamed from spec/frontend/groups/members/index_spec.js)20
-rw-r--r--spec/frontend/members/mock_data.js6
-rw-r--r--spec/frontend/members/store/actions_spec.js20
-rw-r--r--spec/frontend/members/store/mutations_spec.js66
-rw-r--r--spec/frontend/members/utils_spec.js100
-rw-r--r--spec/frontend/merge_request/components/status_box_spec.js10
-rw-r--r--spec/frontend/merge_request_spec.js2
-rw-r--r--spec/frontend/merge_request_tabs_spec.js3
-rw-r--r--spec/frontend/milestones/milestone_combobox_spec.js8
-rw-r--r--spec/frontend/milestones/stores/actions_spec.js2
-rw-r--r--spec/frontend/milestones/stores/mutations_spec.js4
-rw-r--r--spec/frontend/mini_pipeline_graph_dropdown_spec.js2
-rw-r--r--spec/frontend/monitoring/alert_widget_spec.js4
-rw-r--r--spec/frontend/monitoring/components/alert_widget_form_spec.js2
-rw-r--r--spec/frontend/monitoring/components/charts/anomaly_spec.js4
-rw-r--r--spec/frontend/monitoring/components/charts/bar_spec.js2
-rw-r--r--spec/frontend/monitoring/components/charts/column_spec.js2
-rw-r--r--spec/frontend/monitoring/components/charts/gauge_spec.js2
-rw-r--r--spec/frontend/monitoring/components/charts/heatmap_spec.js2
-rw-r--r--spec/frontend/monitoring/components/charts/single_stat_spec.js13
-rw-r--r--spec/frontend/monitoring/components/charts/stacked_column_spec.js4
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js13
-rw-r--r--spec/frontend/monitoring/components/create_dashboard_modal_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboard_actions_menu_spec.js14
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js14
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_builder_spec.js9
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js46
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js24
-rw-r--r--spec/frontend/monitoring/components/dashboard_template_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js6
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js4
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js2
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js8
-rw-r--r--spec/frontend/monitoring/components/embeds/embed_group_spec.js2
-rw-r--r--spec/frontend/monitoring/components/embeds/metric_embed_spec.js2
-rw-r--r--spec/frontend/monitoring/components/empty_state_spec.js4
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js2
-rw-r--r--spec/frontend/monitoring/components/links_section_spec.js4
-rw-r--r--spec/frontend/monitoring/components/refresh_button_spec.js4
-rw-r--r--spec/frontend/monitoring/components/variables/dropdown_field_spec.js2
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js2
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js4
-rw-r--r--spec/frontend/monitoring/csv_export_spec.js2
-rw-r--r--spec/frontend/monitoring/fixture_data.js6
-rw-r--r--spec/frontend/monitoring/graph_data.js2
-rw-r--r--spec/frontend/monitoring/mock_data.js2
-rw-r--r--spec/frontend/monitoring/pages/dashboard_page_spec.js4
-rw-r--r--spec/frontend/monitoring/pages/panel_new_page_spec.js7
-rw-r--r--spec/frontend/monitoring/requests/index_spec.js4
-rw-r--r--spec/frontend/monitoring/router_spec.js4
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js32
-rw-r--r--spec/frontend/monitoring/store/embed_group/mutations_spec.js4
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js6
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js8
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js4
-rw-r--r--spec/frontend/monitoring/store/variable_mapping_spec.js2
-rw-r--r--spec/frontend/monitoring/store_utils.js2
-rw-r--r--spec/frontend/monitoring/utils_spec.js6
-rw-r--r--spec/frontend/namespace_select_spec.js2
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js2
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js143
-rw-r--r--spec/frontend/notes/components/diff_discussion_header_spec.js4
-rw-r--r--spec/frontend/notes/components/diff_with_note_spec.js2
-rw-r--r--spec/frontend/notes/components/discussion_actions_spec.js2
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js8
-rw-r--r--spec/frontend/notes/components/discussion_filter_note_spec.js2
-rw-r--r--spec/frontend/notes/components/discussion_filter_spec.js4
-rw-r--r--spec/frontend/notes/components/discussion_navigator_spec.js2
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js27
-rw-r--r--spec/frontend/notes/components/discussion_resolve_button_spec.js2
-rw-r--r--spec/frontend/notes/components/multiline_comment_form_spec.js4
-rw-r--r--spec/frontend/notes/components/note_actions/reply_button_spec.js19
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js11
-rw-r--r--spec/frontend/notes/components/note_awards_list_spec.js4
-rw-r--r--spec/frontend/notes/components/note_body_spec.js56
-rw-r--r--spec/frontend/notes/components/note_form_spec.js32
-rw-r--r--spec/frontend/notes/components/note_header_spec.js11
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js8
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js19
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js12
-rw-r--r--spec/frontend/notes/components/sort_discussion_spec.js4
-rw-r--r--spec/frontend/notes/components/timeline_toggle_spec.js4
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js6
-rw-r--r--spec/frontend/notes/old_notes_spec.js6
-rw-r--r--spec/frontend/notes/stores/actions_spec.js84
-rw-r--r--spec/frontend/notes/stores/getters_spec.js2
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js15
-rw-r--r--spec/frontend/notifications/components/custom_notifications_modal_spec.js267
-rw-r--r--spec/frontend/notifications/components/notifications_dropdown_spec.js274
-rw-r--r--spec/frontend/onboarding_issues/index_spec.js137
-rw-r--r--spec/frontend/operation_settings/components/metrics_settings_spec.js14
-rw-r--r--spec/frontend/operation_settings/store/mutations_spec.js2
-rw-r--r--spec/frontend/packages/details/components/additional_metadata_spec.js4
-rw-r--r--spec/frontend/packages/details/components/app_spec.js25
-rw-r--r--spec/frontend/packages/details/components/composer_installation_spec.js4
-rw-r--r--spec/frontend/packages/details/components/conan_installation_spec.js2
-rw-r--r--spec/frontend/packages/details/components/installations_commands_spec.js6
-rw-r--r--spec/frontend/packages/details/components/maven_installation_spec.js4
-rw-r--r--spec/frontend/packages/details/components/npm_installation_spec.js6
-rw-r--r--spec/frontend/packages/details/components/nuget_installation_spec.js6
-rw-r--r--spec/frontend/packages/details/components/package_files_spec.js4
-rw-r--r--spec/frontend/packages/details/components/package_history_spec.js8
-rw-r--r--spec/frontend/packages/details/components/package_title_spec.js4
-rw-r--r--spec/frontend/packages/details/components/pypi_installation_spec.js2
-rw-r--r--spec/frontend/packages/details/store/actions_spec.js2
-rw-r--r--spec/frontend/packages/details/store/getters_spec.js2
-rw-r--r--spec/frontend/packages/details/store/mutations_spec.js2
-rw-r--r--spec/frontend/packages/list/components/__snapshots__/packages_filter_spec.js.snap14
-rw-r--r--spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap555
-rw-r--r--spec/frontend/packages/list/components/packages_filter_spec.js50
-rw-r--r--spec/frontend/packages/list/components/packages_list_app_spec.js65
-rw-r--r--spec/frontend/packages/list/components/packages_list_spec.js10
-rw-r--r--spec/frontend/packages/list/components/packages_search_spec.js107
-rw-r--r--spec/frontend/packages/list/components/packages_sort_spec.js90
-rw-r--r--spec/frontend/packages/list/components/packages_title_spec.js4
-rw-r--r--spec/frontend/packages/list/components/tokens/package_type_token_spec.js48
-rw-r--r--spec/frontend/packages/list/stores/actions_spec.js14
-rw-r--r--spec/frontend/packages/list/stores/mutations_spec.js13
-rw-r--r--spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap10
-rw-r--r--spec/frontend/packages/shared/components/package_list_row_spec.js2
-rw-r--r--spec/frontend/packages/shared/components/packages_list_loader_spec.js4
-rw-r--r--spec/frontend/packages/shared/utils_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js309
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js153
-rw-r--r--spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js56
-rw-r--r--spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js20
-rw-r--r--spec/frontend/packages_and_registries/settings/group/mock_data.js48
-rw-r--r--spec/frontend/pager_spec.js4
-rw-r--r--spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js4
-rw-r--r--spec/frontend/pages/admin/users/components/delete_user_modal_spec.js2
-rw-r--r--spec/frontend/pages/admin/users/components/user_modal_manager_spec.js97
-rw-r--r--spec/frontend/pages/dashboard/projects/index/components/customize_homepage_banner_spec.js4
-rw-r--r--spec/frontend/pages/dashboard/todos/index/todos_spec.js4
-rw-r--r--spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js4
-rw-r--r--spec/frontend/pages/labels/components/promote_label_modal_spec.js4
-rw-r--r--spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js4
-rw-r--r--spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js6
-rw-r--r--spec/frontend/pages/projects/edit/mount_search_settings_spec.js25
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js7
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js11
-rw-r--r--spec/frontend/pages/projects/graphs/code_coverage_spec.js6
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap66
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap66
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js63
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js63
-rw-r--r--spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js41
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js2
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js103
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js389
-rw-r--r--spec/frontend/performance_bar/components/detailed_metric_spec.js2
-rw-r--r--spec/frontend/persistent_user_callout_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_form_spec.js8
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_section_spec.js223
-rw-r--r--spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js88
-rw-r--r--spec/frontend/pipeline_editor/components/editor/text_editor_spec.js120
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js34
-rw-r--r--spec/frontend/pipeline_editor/components/header/validation_segment_spec.js (renamed from spec/frontend/pipeline_editor/components/info/validation_segment_spec.js)13
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js4
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js183
-rw-r--r--spec/frontend/pipeline_editor/components/text_editor_spec.js93
-rw-r--r--spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js42
-rw-r--r--spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js4
-rw-r--r--spec/frontend/pipeline_editor/graphql/resolvers_spec.js6
-rw-r--r--spec/frontend/pipeline_editor/mock_data.js3
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js425
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_home_spec.js78
-rw-r--r--spec/frontend/pipeline_new/components/pipeline_new_form_spec.js129
-rw-r--r--spec/frontend/pipeline_new/utils/format_refs_spec.js2
-rw-r--r--spec/frontend/pipelines/blank_state_spec.js21
-rw-r--r--spec/frontend/pipelines/components/dag/dag_annotations_spec.js2
-rw-r--r--spec/frontend/pipelines/components/dag/dag_graph_spec.js4
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js6
-rw-r--r--spec/frontend/pipelines/components/dag/parsing_utils_spec.js2
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js105
-rw-r--r--spec/frontend/pipelines/graph/action_component_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/graph_component_legacy_spec.js12
-rw-r--r--spec/frontend/pipelines/graph/graph_component_spec.js16
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js6
-rw-r--r--spec/frontend/pipelines/graph/job_name_component_spec.js3
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js9
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js113
-rw-r--r--spec/frontend/pipelines/graph/stage_column_component_spec.js4
-rw-r--r--spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap23
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js197
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js (renamed from spec/frontend/pipelines/shared/links_layer_spec.js)4
-rw-r--r--spec/frontend/pipelines/header_component_spec.js10
-rw-r--r--spec/frontend/pipelines/legacy_header_component_spec.js116
-rw-r--r--spec/frontend/pipelines/pipeline_graph/mock_data.js135
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js8
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js28
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js66
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js990
-rw-r--r--spec/frontend/pipelines/pipelines_table_row_spec.js4
-rw-r--r--spec/frontend/pipelines/stage_spec.js278
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js7
-rw-r--r--spec/frontend/pipelines/test_reports/stores/getters_spec.js73
-rw-r--r--spec/frontend/pipelines/test_reports/stores/utils_spec.js16
-rw-r--r--spec/frontend/pipelines/test_reports/test_case_details_spec.js37
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js2
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js44
-rw-r--r--spec/frontend/pipelines/test_reports/test_summary_table_spec.js2
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js2
-rw-r--r--spec/frontend/popovers/components/popovers_spec.js2
-rw-r--r--spec/frontend/profile/account/components/delete_account_modal_spec.js4
-rw-r--r--spec/frontend/profile/account/components/update_username_spec.js2
-rw-r--r--spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap67
-rw-r--r--spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap51
-rw-r--r--spec/frontend/profile/preferences/components/integration_view_spec.js10
-rw-r--r--spec/frontend/profile/preferences/components/profile_preferences_spec.js142
-rw-r--r--spec/frontend/profile/preferences/mock_data.js2
-rw-r--r--spec/frontend/project_find_file_spec.js2
-rw-r--r--spec/frontend/projects/commit/components/branches_dropdown_spec.js4
-rw-r--r--spec/frontend/projects/commit/components/form_modal_spec.js13
-rw-r--r--spec/frontend/projects/commit/components/form_trigger_spec.js2
-rw-r--r--spec/frontend/projects/commit/store/actions_spec.js6
-rw-r--r--spec/frontend/projects/commit/store/mutations_spec.js2
-rw-r--r--spec/frontend/projects/commit_box/info/load_branches_spec.js2
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js2
-rw-r--r--spec/frontend/projects/commits/store/actions_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/app_spec.js116
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_spec.js92
-rw-r--r--spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap4
-rw-r--r--spec/frontend/projects/components/shared/delete_button_spec.js2
-rw-r--r--spec/frontend/projects/experiment_new_project_creation/components/app_spec.js4
-rw-r--r--spec/frontend/projects/members/utils_spec.js14
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js168
-rw-r--r--spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js94
-rw-r--r--spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js72
-rw-r--r--spec/frontend/projects/pipelines/charts/mock_data.js10
-rw-r--r--spec/frontend/projects/settings/access_dropdown_spec.js1
-rw-r--r--spec/frontend/projects/settings/components/shared_runners_toggle_spec.js2
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js236
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js190
-rw-r--r--spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js111
-rw-r--r--spec/frontend/prometheus_alerts/components/reset_key_spec.js4
-rw-r--r--spec/frontend/prometheus_metrics/custom_metrics_spec.js2
-rw-r--r--spec/frontend/prometheus_metrics/prometheus_metrics_spec.js2
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js6
-rw-r--r--spec/frontend/ref/stores/actions_spec.js2
-rw-r--r--spec/frontend/ref/stores/mutations_spec.js6
-rw-r--r--spec/frontend/registry/explorer/components/delete_button_spec.js2
-rw-r--r--spec/frontend/registry/explorer/components/delete_image_spec.js152
-rw-r--r--spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js2
-rw-r--r--spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js68
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_header_spec.js85
-rw-r--r--spec/frontend/registry/explorer/components/details_page/empty_state_spec.js54
-rw-r--r--spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js43
-rw-r--r--spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js2
-rw-r--r--spec/frontend/registry/explorer/components/details_page/status_alert_spec.js57
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js10
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_spec.js32
-rw-r--r--spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js8
-rw-r--r--spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js6
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js10
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_spec.js2
-rw-r--r--spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js6
-rw-r--r--spec/frontend/registry/explorer/components/list_page/registry_header_spec.js4
-rw-r--r--spec/frontend/registry/explorer/mock_data.js6
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js160
-rw-r--r--spec/frontend/registry/explorer/pages/list_spec.js122
-rw-r--r--spec/frontend/registry/settings/components/expiration_input_spec.js2
-rw-r--r--spec/frontend/registry/settings/components/expiration_run_text_spec.js2
-rw-r--r--spec/frontend/registry/settings/components/expiration_toggle_spec.js2
-rw-r--r--spec/frontend/registry/settings/components/registry_settings_app_spec.js4
-rw-r--r--spec/frontend/registry/settings/components/settings_form_spec.js6
-rw-r--r--spec/frontend/registry/settings/graphql/cache_updated_spec.js2
-rw-r--r--spec/frontend/related_merge_requests/components/related_merge_requests_spec.js2
-rw-r--r--spec/frontend/related_merge_requests/store/actions_spec.js2
-rw-r--r--spec/frontend/related_merge_requests/store/mutations_spec.js2
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js8
-rw-r--r--spec/frontend/releases/components/app_index_spec.js10
-rw-r--r--spec/frontend/releases/components/app_show_spec.js6
-rw-r--r--spec/frontend/releases/components/asset_links_form_spec.js4
-rw-r--r--spec/frontend/releases/components/evidence_block_spec.js4
-rw-r--r--spec/frontend/releases/components/release_block_assets_spec.js4
-rw-r--r--spec/frontend/releases/components/release_block_footer_spec.js32
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js4
-rw-r--r--spec/frontend/releases/components/release_block_milestone_info_spec.js6
-rw-r--r--spec/frontend/releases/components/release_block_spec.js8
-rw-r--r--spec/frontend/releases/components/release_skeleton_loader_spec.js2
-rw-r--r--spec/frontend/releases/components/releases_pagination_graphql_spec.js6
-rw-r--r--spec/frontend/releases/components/releases_pagination_rest_spec.js6
-rw-r--r--spec/frontend/releases/components/releases_sort_spec.js2
-rw-r--r--spec/frontend/releases/components/tag_field_exsting_spec.js2
-rw-r--r--spec/frontend/releases/components/tag_field_new_spec.js4
-rw-r--r--spec/frontend/releases/components/tag_field_spec.js2
-rw-r--r--spec/frontend/releases/stores/modules/detail/actions_spec.js14
-rw-r--r--spec/frontend/releases/stores/modules/detail/mutations_spec.js6
-rw-r--r--spec/frontend/releases/stores/modules/list/actions_spec.js20
-rw-r--r--spec/frontend/releases/stores/modules/list/mutations_spec.js8
-rw-r--r--spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js2
-rw-r--r--spec/frontend/reports/accessibility_report/store/actions_spec.js4
-rw-r--r--spec/frontend/reports/accessibility_report/store/getters_spec.js2
-rw-r--r--spec/frontend/reports/accessibility_report/store/mutations_spec.js2
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js7
-rw-r--r--spec/frontend/reports/codequality_report/mock_data.js50
-rw-r--r--spec/frontend/reports/codequality_report/store/actions_spec.js173
-rw-r--r--spec/frontend/reports/codequality_report/store/getters_spec.js2
-rw-r--r--spec/frontend/reports/codequality_report/store/mutations_spec.js16
-rw-r--r--spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js18
-rw-r--r--spec/frontend/reports/components/grouped_test_reports_app_spec.js9
-rw-r--r--spec/frontend/reports/components/modal_spec.js2
-rw-r--r--spec/frontend/reports/components/report_item_spec.js6
-rw-r--r--spec/frontend/reports/components/report_section_spec.js2
-rw-r--r--spec/frontend/reports/components/summary_row_spec.js4
-rw-r--r--spec/frontend/reports/components/test_issue_body_spec.js2
-rw-r--r--spec/frontend/reports/store/actions_spec.js4
-rw-r--r--spec/frontend/reports/store/mutations_spec.js4
-rw-r--r--spec/frontend/reports/store/utils_spec.js2
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js2
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js98
-rw-r--r--spec/frontend/repository/components/preview/index_spec.js2
-rw-r--r--spec/frontend/repository/components/table/index_spec.js2
-rw-r--r--spec/frontend/repository/components/table/parent_row_spec.js2
-rw-r--r--spec/frontend/repository/components/table/row_spec.js2
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js2
-rw-r--r--spec/frontend/repository/utils/dom_spec.js2
-rw-r--r--spec/frontend/right_sidebar_spec.js2
-rw-r--r--spec/frontend/search/highlight_blob_search_result_spec.js3
-rw-r--r--spec/frontend/search/index_spec.js3
-rw-r--r--spec/frontend/search/mock_data.js22
-rw-r--r--spec/frontend/search/sidebar/components/app_spec.js4
-rw-r--r--spec/frontend/search/sidebar/components/confidentiality_filter_spec.js2
-rw-r--r--spec/frontend/search/sidebar/components/radio_filter_spec.js6
-rw-r--r--spec/frontend/search/sidebar/components/status_filter_spec.js4
-rw-r--r--spec/frontend/search/sort/components/app_spec.js168
-rw-r--r--spec/frontend/search/store/actions_spec.js6
-rw-r--r--spec/frontend/search/store/mutations_spec.js2
-rw-r--r--spec/frontend/search/topbar/components/app_spec.js113
-rw-r--r--spec/frontend/search/topbar/components/group_filter_spec.js2
-rw-r--r--spec/frontend/search/topbar/components/project_filter_spec.js4
-rw-r--r--spec/frontend/search/topbar/components/searchable_dropdown_spec.js4
-rw-r--r--spec/frontend/search_autocomplete_spec.js5
-rw-r--r--spec/frontend/search_settings/components/search_settings_spec.js100
-rw-r--r--spec/frontend/search_settings/index_spec.js37
-rw-r--r--spec/frontend/search_settings/mount_spec.js35
-rw-r--r--spec/frontend/search_spec.js23
-rw-r--r--spec/frontend/security_configuration/app_spec.js27
-rw-r--r--spec/frontend/security_configuration/configuration_table_spec.js48
-rw-r--r--spec/frontend/security_configuration/manage_sast_spec.js136
-rw-r--r--spec/frontend/security_configuration/upgrade_spec.js29
-rw-r--r--spec/frontend/self_monitor/components/self_monitor_form_spec.js2
-rw-r--r--spec/frontend/sentry/index_spec.js2
-rw-r--r--spec/frontend/sentry/sentry_config_spec.js2
-rw-r--r--spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js2
-rw-r--r--spec/frontend/serverless/components/empty_state_spec.js2
-rw-r--r--spec/frontend/serverless/components/environment_row_spec.js2
-rw-r--r--spec/frontend/serverless/components/function_details_spec.js2
-rw-r--r--spec/frontend/serverless/components/functions_spec.js8
-rw-r--r--spec/frontend/serverless/components/missing_prometheus_spec.js2
-rw-r--r--spec/frontend/serverless/components/url_spec.js2
-rw-r--r--spec/frontend/serverless/store/actions_spec.js2
-rw-r--r--spec/frontend/serverless/store/getters_spec.js2
-rw-r--r--spec/frontend/serverless/store/mutations_spec.js2
-rw-r--r--spec/frontend/serverless/survey_banner_spec.js2
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js2
-rw-r--r--spec/frontend/set_status_modal/user_availability_status_spec.js31
-rw-r--r--spec/frontend/set_status_modal/utils_spec.js15
-rw-r--r--spec/frontend/settings_panels_spec.js10
-rw-r--r--spec/frontend/sidebar/__snapshots__/todo_spec.js.snap2
-rw-r--r--spec/frontend/sidebar/assignee_title_spec.js2
-rw-r--r--spec/frontend/sidebar/assignees_realtime_spec.js4
-rw-r--r--spec/frontend/sidebar/assignees_spec.js2
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js32
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js26
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js6
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js120
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js2
-rw-r--r--spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js51
-rw-r--r--spec/frontend/sidebar/components/copy_email_to_clipboard_spec.js4
-rw-r--r--spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js91
-rw-r--r--spec/frontend/sidebar/components/severity/severity_spec.js4
-rw-r--r--spec/frontend/sidebar/components/severity/sidebar_severity_spec.js8
-rw-r--r--spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js2
-rw-r--r--spec/frontend/sidebar/confidential/edit_form_buttons_spec.js4
-rw-r--r--spec/frontend/sidebar/confidential_issue_sidebar_spec.js6
-rw-r--r--spec/frontend/sidebar/lock/edit_form_buttons_spec.js6
-rw-r--r--spec/frontend/sidebar/lock/issuable_lock_form_spec.js6
-rw-r--r--spec/frontend/sidebar/reviewer_title_spec.js2
-rw-r--r--spec/frontend/sidebar/reviewers_spec.js9
-rw-r--r--spec/frontend/sidebar/sidebar_assignees_spec.js6
-rw-r--r--spec/frontend/sidebar/sidebar_move_issue_spec.js6
-rw-r--r--spec/frontend/sidebar/sidebar_subscriptions_spec.js2
-rw-r--r--spec/frontend/sidebar/subscriptions_spec.js25
-rw-r--r--spec/frontend/sidebar/todo_spec.js6
-rw-r--r--spec/frontend/sidebar/user_data_mock.js2
-rw-r--r--spec/frontend/snippets/components/edit_spec.js585
-rw-r--r--spec/frontend/snippets/components/embed_dropdown_spec.js4
-rw-r--r--spec/frontend/snippets/components/show_spec.js9
-rw-r--r--spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js2
-rw-r--r--spec/frontend/snippets/components/snippet_blob_edit_spec.js8
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js8
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js8
-rw-r--r--spec/frontend/snippets/components/snippet_title_spec.js2
-rw-r--r--spec/frontend/snippets/test_utils.js55
-rw-r--r--spec/frontend/snippets/utils/error_spec.js16
-rw-r--r--spec/frontend/static_site_editor/components/edit_area_spec.js11
-rw-r--r--spec/frontend/static_site_editor/components/edit_drawer_spec.js3
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_controls_spec.js3
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_modal_spec.js8
-rw-r--r--spec/frontend/static_site_editor/components/front_matter_controls_spec.js2
-rw-r--r--spec/frontend/static_site_editor/components/submit_changes_error_spec.js2
-rw-r--r--spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js2
-rw-r--r--spec/frontend/static_site_editor/pages/home_spec.js8
-rw-r--r--spec/frontend/static_site_editor/pages/success_spec.js4
-rw-r--r--spec/frontend/static_site_editor/services/front_matterify_spec.js3
-rw-r--r--spec/frontend/static_site_editor/services/parse_source_file_spec.js3
-rw-r--r--spec/frontend/task_list_spec.js2
-rw-r--r--spec/frontend/terraform/components/states_table_actions_spec.js181
-rw-r--r--spec/frontend/terraform/components/states_table_spec.js56
-rw-r--r--spec/frontend/terraform/components/terraform_list_spec.js41
-rw-r--r--spec/frontend/test_setup.js14
-rw-r--r--spec/frontend/tooltips/components/tooltips_spec.js2
-rw-r--r--spec/frontend/tooltips/index_spec.js26
-rw-r--r--spec/frontend/user_lists/components/edit_user_list_spec.js8
-rw-r--r--spec/frontend/user_lists/components/new_user_list_spec.js6
-rw-r--r--spec/frontend/user_lists/components/user_list_spec.js10
-rw-r--r--spec/frontend/user_lists/store/edit/actions_spec.js6
-rw-r--r--spec/frontend/user_lists/store/edit/mutations_spec.js2
-rw-r--r--spec/frontend/user_lists/store/new/actions_spec.js6
-rw-r--r--spec/frontend/user_lists/store/new/mutations_spec.js2
-rw-r--r--spec/frontend/user_lists/store/show/actions_spec.js6
-rw-r--r--spec/frontend/user_lists/store/show/mutations_spec.js4
-rw-r--r--spec/frontend/user_popovers_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/components/artifacts_list_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js46
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js12
-rw-r--r--spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap24
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js12
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js24
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js26
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js39
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js14
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js8
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js83
-rw-r--r--spec/frontend/vue_mr_widget/stores/artifacts_list/actions_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/stores/artifacts_list/mutations_spec.js4
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_details_spec.js (renamed from spec/frontend/alert_management/components/alert_details_spec.js)59
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js (renamed from spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js)8
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_metrics_spec.js (renamed from spec/frontend/alert_management/components/alert_metrics_spec.js)6
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_status_spec.js (renamed from spec/frontend/alert_management/components/alert_status_spec.js)29
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js (renamed from spec/frontend/alert_management/components/alert_summary_row_spec.js)2
-rw-r--r--spec/frontend/vue_shared/alert_details/mocks/alerts.json (renamed from spec/frontend/alert_management/mocks/alerts.json)0
-rw-r--r--spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js (renamed from spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js)10
-rw-r--r--spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js (renamed from spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js)39
-rw-r--r--spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js (renamed from spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js)35
-rw-r--r--spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js (renamed from spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js)6
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap28
-rw-r--r--spec/frontend/vue_shared/components/actions_button_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/ci_badge_link_spec.js40
-rw-r--r--spec/frontend/vue_shared/components/ci_icon_spec.js151
-rw-r--r--spec/frontend/vue_shared/components/clipboard_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/clone_dropdown_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/color_picker/color_picker_spec.js39
-rw-r--r--spec/frontend/vue_shared/components/commit_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/dismissible_alert_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/dismissible_container_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/editor_lite_spec.js11
-rw-r--r--spec/frontend/vue_shared/components/expand_button_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/file_finder/index_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/file_finder/item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/file_icon_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/file_row_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/file_tree_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap16
-rw-r--r--spec/frontend/vue_shared/components/gl_countdown_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/header_ci_component_spec.js118
-rw-r--r--spec/frontend/vue_shared/components/help_popover_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/integration_help_text_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_assignees_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_milestone_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_view_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/memory_graph_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/multiselect_dropdown_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/navigation_tabs_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/notes/noteable_warning_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/notes/system_note_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/pagination_links_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/pikaday_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_selector_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/registry/code_instruction_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/registry/details_row_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/registry/history_item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/registry/metadata_item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/registry/registry_search_spec.js105
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/mock_data.js107
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js113
-rw-r--r--spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap43
-rw-r--r--spec/frontend/vue_shared/components/settings/settings_block_spec.js86
-rw-r--r--spec/frontend/vue_shared/components/sidebar/date_picker_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js42
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js42
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/smart_virtual_list_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/table_pagination_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/tabs/tabs_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/todo_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/toggle_button_spec.js96
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js2
-rw-r--r--spec/frontend/vue_shared/directives/tooltip_spec.js2
-rw-r--r--spec/frontend/vue_shared/directives/track_event_spec.js4
-rw-r--r--spec/frontend/vue_shared/security_reports/mock_data.js17
-rw-r--r--spec/frontend/vue_shared/security_reports/security_reports_app_spec.js374
-rw-r--r--spec/frontend/vue_shared/security_reports/store/getters_spec.js8
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js6
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js2
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js6
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js2
-rw-r--r--spec/frontend/vue_shared/security_reports/utils_spec.js2
-rw-r--r--spec/frontend/vuex_shared/modules/modal/actions_spec.js2
-rw-r--r--spec/frontend/vuex_shared/modules/modal/mutations_spec.js2
-rw-r--r--spec/frontend/whats_new/components/app_spec.js2
-rw-r--r--spec/frontend/whats_new/store/actions_spec.js6
-rw-r--r--spec/frontend/whats_new/store/mutations_spec.js2
-rw-r--r--spec/frontend/whats_new/utils/get_drawer_body_height_spec.js2
-rw-r--r--spec/frontend/zen_mode_spec.js4
1192 files changed, 17319 insertions, 9947 deletions
diff --git a/spec/frontend/__helpers__/emoji.js b/spec/frontend/__helpers__/emoji.js
index ea6613b53c9..9f9134f6f63 100644
--- a/spec/frontend/__helpers__/emoji.js
+++ b/spec/frontend/__helpers__/emoji.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
+import axios from '~/lib/utils/axios_utils';
export const emojiFixtureMap = {
atom: {
@@ -29,10 +29,6 @@ export const emojiFixtureMap = {
unicodeVersion: '6.0',
description: 'white question mark ornament',
},
-
- // used for regression tests
- // black_heart MUST come before heart
- // custard MUST come before star
black_heart: {
moji: '🖤',
unicodeVersion: '1.1',
@@ -55,34 +51,18 @@ export const emojiFixtureMap = {
},
};
-Object.keys(emojiFixtureMap).forEach((k) => {
- emojiFixtureMap[k].name = k;
- if (!emojiFixtureMap[k].aliases) {
- emojiFixtureMap[k].aliases = [];
- }
-});
+export const mockEmojiData = Object.keys(emojiFixtureMap).reduce((acc, k) => {
+ const { moji: e, unicodeVersion: u, category: c, description: d } = emojiFixtureMap[k];
+ acc[k] = { name: k, e, u, c, d };
-export async function initEmojiMock() {
- const emojiData = Object.fromEntries(
- Object.values(emojiFixtureMap).map((m) => {
- const { name: n, moji: e, unicodeVersion: u, category: c, description: d } = m;
- return [n, { c, e, d, u }];
- }),
- );
+ return acc;
+}, {});
+export async function initEmojiMock(mockData = mockEmojiData) {
const mock = new MockAdapter(axios);
- mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, JSON.stringify(emojiData));
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, JSON.stringify(mockData));
await initEmojiMap();
return mock;
}
-
-export function describeEmojiFields(label, tests) {
- describe.each`
- field | accessor
- ${'name'} | ${(e) => e.name}
- ${'alias'} | ${(e) => e.aliases[0]}
- ${'description'} | ${(e) => e.description}
- `(label, tests);
-}
diff --git a/spec/frontend/__helpers__/fake_date.js b/spec/frontend/__helpers__/fake_date/fake_date.js
index 5391ae04797..bc088ad96b6 100644
--- a/spec/frontend/__helpers__/fake_date.js
+++ b/spec/frontend/__helpers__/fake_date/fake_date.js
@@ -1,11 +1,13 @@
// Frida Kahlo's birthday (6 = July)
-export const DEFAULT_ARGS = [2020, 6, 6];
+const DEFAULT_ARGS = [2020, 6, 6];
const RealDate = Date;
const isMocked = (val) => Boolean(val.mock);
-export const createFakeDateClass = (ctorDefault) => {
+const createFakeDateClass = (ctorDefaultParam = []) => {
+ const ctorDefault = ctorDefaultParam.length ? ctorDefaultParam : DEFAULT_ARGS;
+
const FakeDate = new Proxy(RealDate, {
construct: (target, argArray) => {
const ctorArgs = argArray.length ? argArray : ctorDefault;
@@ -39,11 +41,20 @@ export const createFakeDateClass = (ctorDefault) => {
return FakeDate;
};
-export const useFakeDate = (...args) => {
- const FakeDate = createFakeDateClass(args.length ? args : DEFAULT_ARGS);
+const setGlobalDateToFakeDate = (...args) => {
+ const FakeDate = createFakeDateClass(args);
global.Date = FakeDate;
};
-export const useRealDate = () => {
+const setGlobalDateToRealDate = () => {
global.Date = RealDate;
};
+
+// We use commonjs so that the test environment module can pick this up
+// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ setGlobalDateToFakeDate,
+ setGlobalDateToRealDate,
+ createFakeDateClass,
+ RealDate,
+};
diff --git a/spec/frontend/__helpers__/fake_date_spec.js b/spec/frontend/__helpers__/fake_date/fake_date_spec.js
index b3ed13e238a..730765e52d2 100644
--- a/spec/frontend/__helpers__/fake_date_spec.js
+++ b/spec/frontend/__helpers__/fake_date/fake_date_spec.js
@@ -1,15 +1,11 @@
-import { createFakeDateClass, DEFAULT_ARGS, useRealDate } from './fake_date';
+import { createFakeDateClass } from './fake_date';
describe('spec/helpers/fake_date', () => {
describe('createFakeDateClass', () => {
let FakeDate;
- beforeAll(() => {
- useRealDate();
- });
-
beforeEach(() => {
- FakeDate = createFakeDateClass(DEFAULT_ARGS);
+ FakeDate = createFakeDateClass();
});
it('should use default args', () => {
diff --git a/spec/frontend/__helpers__/fake_date/index.js b/spec/frontend/__helpers__/fake_date/index.js
new file mode 100644
index 00000000000..3d1b124ce79
--- /dev/null
+++ b/spec/frontend/__helpers__/fake_date/index.js
@@ -0,0 +1,2 @@
+export * from './fake_date';
+export * from './jest';
diff --git a/spec/frontend/__helpers__/fake_date/jest.js b/spec/frontend/__helpers__/fake_date/jest.js
new file mode 100644
index 00000000000..65e45619049
--- /dev/null
+++ b/spec/frontend/__helpers__/fake_date/jest.js
@@ -0,0 +1,41 @@
+import { createJestExecutionWatcher } from '../jest_execution_watcher';
+import { RealDate, createFakeDateClass } from './fake_date';
+
+const throwInsideExecutionError = (fnName) => {
+ throw new Error(`Cannot call "${fnName}" during test execution (i.e. within "it", "beforeEach", "beforeAll", etc.).
+
+Instead, please move the call to "${fnName}" inside the "describe" block itself.
+
+ describe('', () => {
+ + ${fnName}();
+
+ it('', () => {
+ - ${fnName}();
+ })
+ })
+`);
+};
+
+const isExecutingTest = createJestExecutionWatcher();
+
+export const useDateInScope = (fnName, factory) => {
+ if (isExecutingTest()) {
+ throwInsideExecutionError(fnName);
+ }
+
+ let origDate;
+
+ beforeAll(() => {
+ origDate = global.Date;
+ global.Date = factory();
+ });
+
+ afterAll(() => {
+ global.Date = origDate;
+ });
+};
+
+export const useFakeDate = (...args) =>
+ useDateInScope('useFakeDate', () => createFakeDateClass(args));
+
+export const useRealDate = () => useDateInScope('useRealDate', () => RealDate);
diff --git a/spec/frontend/__helpers__/graphql_helpers.js b/spec/frontend/__helpers__/graphql_helpers.js
new file mode 100644
index 00000000000..63123aa046f
--- /dev/null
+++ b/spec/frontend/__helpers__/graphql_helpers.js
@@ -0,0 +1,14 @@
+/**
+ * Returns a clone of the given object with all __typename keys omitted,
+ * including deeply nested ones.
+ *
+ * Only works with JSON-serializable objects.
+ *
+ * @param {object} An object with __typename keys (e.g., a GraphQL response)
+ * @returns {object} A new object with no __typename keys
+ */
+export const stripTypenames = (object) => {
+ return JSON.parse(
+ JSON.stringify(object, (key, value) => (key === '__typename' ? undefined : value)),
+ );
+};
diff --git a/spec/frontend/__helpers__/graphql_helpers_spec.js b/spec/frontend/__helpers__/graphql_helpers_spec.js
new file mode 100644
index 00000000000..dd23fbbf4e9
--- /dev/null
+++ b/spec/frontend/__helpers__/graphql_helpers_spec.js
@@ -0,0 +1,23 @@
+import { stripTypenames } from './graphql_helpers';
+
+describe('stripTypenames', () => {
+ it.each`
+ input | expected
+ ${{}} | ${{}}
+ ${{ __typename: 'Foo' }} | ${{}}
+ ${{ bar: 'bar', __typename: 'Foo' }} | ${{ bar: 'bar' }}
+ ${{ bar: { __typename: 'Bar' }, __typename: 'Foo' }} | ${{ bar: {} }}
+ ${{ bar: [{ __typename: 'Bar' }], __typename: 'Foo' }} | ${{ bar: [{}] }}
+ ${[]} | ${[]}
+ ${[{ __typename: 'Foo' }]} | ${[{}]}
+ ${[{ bar: [{ a: 1, __typename: 'Bar' }] }]} | ${[{ bar: [{ a: 1 }] }]}
+ `('given $input returns $expected, with all __typename keys removed', ({ input, expected }) => {
+ const actual = stripTypenames(input);
+ expect(actual).toEqual(expected);
+ expect(input).not.toBe(actual);
+ });
+
+ it('given null returns null', () => {
+ expect(stripTypenames(null)).toEqual(null);
+ });
+});
diff --git a/spec/frontend/__helpers__/init_vue_mr_page_helper.js b/spec/frontend/__helpers__/init_vue_mr_page_helper.js
index b9aed63d0f6..ee01e9e6268 100644
--- a/spec/frontend/__helpers__/init_vue_mr_page_helper.js
+++ b/spec/frontend/__helpers__/init_vue_mr_page_helper.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
-import initMRPage from '~/mr_notes';
import axios from '~/lib/utils/axios_utils';
-import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_data';
+import initMRPage from '~/mr_notes';
import diffFileMockData from '../diffs/mock_data/diff_file';
+import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_data';
export default function initVueMRPage() {
const mrTestEl = document.createElement('div');
diff --git a/spec/frontend/__helpers__/jest_execution_watcher.js b/spec/frontend/__helpers__/jest_execution_watcher.js
new file mode 100644
index 00000000000..0fc3d330ec3
--- /dev/null
+++ b/spec/frontend/__helpers__/jest_execution_watcher.js
@@ -0,0 +1,12 @@
+export const createJestExecutionWatcher = () => {
+ let isExecuting = false;
+
+ beforeAll(() => {
+ isExecuting = true;
+ });
+ afterAll(() => {
+ isExecuting = false;
+ });
+
+ return () => isExecuting;
+};
diff --git a/spec/frontend/__helpers__/stub_component.js b/spec/frontend/__helpers__/stub_component.js
index 45550450517..96fe3a8bc45 100644
--- a/spec/frontend/__helpers__/stub_component.js
+++ b/spec/frontend/__helpers__/stub_component.js
@@ -1,7 +1,32 @@
+/**
+ * Returns a new object with keys pointing to stubbed methods
+ *
+ * This is helpful for stubbing components like GlModal where it's supported
+ * in the API to call `.show()` and `.hide()` ([Bootstrap Vue docs][1]).
+ *
+ * [1]: https://bootstrap-vue.org/docs/components/modal#using-show-hide-and-toggle-component-methods
+ *
+ * @param {Object} methods - Object whose keys will be in the returned object.
+ */
+const createStubbedMethods = (methods = {}) => {
+ if (!methods) {
+ return {};
+ }
+
+ return Object.keys(methods).reduce(
+ (acc, key) =>
+ Object.assign(acc, {
+ [key]: () => {},
+ }),
+ {},
+ );
+};
+
export function stubComponent(Component, options = {}) {
return {
props: Component.props,
model: Component.model,
+ methods: createStubbedMethods(Component.methods),
// Do not render any slots/scoped slots except default
// This differs from VTU behavior which renders all slots
template: '<div><slot></slot></div>',
diff --git a/spec/frontend/__mocks__/@gitlab/ui.js b/spec/frontend/__mocks__/@gitlab/ui.js
index 7cdecefab05..ecd67247362 100644
--- a/spec/frontend/__mocks__/@gitlab/ui.js
+++ b/spec/frontend/__mocks__/@gitlab/ui.js
@@ -38,7 +38,9 @@ jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
required: false,
default: () => [],
},
- ...Object.fromEntries(['target', 'triggers', 'placement'].map((prop) => [prop, {}])),
+ ...Object.fromEntries(
+ ['target', 'triggers', 'placement', 'boundary', 'container'].map((prop) => [prop, {}]),
+ ),
},
render(h) {
return h(
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 cd235d0afa5..4a2815e6931 100644
--- a/spec/frontend/access_tokens/components/expires_at_field_spec.js
+++ b/spec/frontend/access_tokens/components/expires_at_field_spec.js
@@ -1,10 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { useFakeDate } from 'helpers/fake_date';
import ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
describe('~/access_tokens/components/expires_at_field', () => {
- useFakeDate();
-
let wrapper;
const createComponent = () => {
diff --git a/spec/frontend/actioncable_connection_monitor_spec.js b/spec/frontend/actioncable_connection_monitor_spec.js
new file mode 100644
index 00000000000..c68eb53acde
--- /dev/null
+++ b/spec/frontend/actioncable_connection_monitor_spec.js
@@ -0,0 +1,79 @@
+import ConnectionMonitor from '~/actioncable_connection_monitor';
+
+describe('ConnectionMonitor', () => {
+ let monitor;
+
+ beforeEach(() => {
+ monitor = new ConnectionMonitor({});
+ });
+
+ describe('#getPollInterval', () => {
+ beforeEach(() => {
+ Math.originalRandom = Math.random;
+ });
+ afterEach(() => {
+ Math.random = Math.originalRandom;
+ });
+
+ const { staleThreshold, reconnectionBackoffRate } = ConnectionMonitor;
+ const backoffFactor = 1 + reconnectionBackoffRate;
+ const ms = 1000;
+
+ it('uses exponential backoff', () => {
+ Math.random = () => 0;
+
+ monitor.reconnectAttempts = 0;
+ expect(monitor.getPollInterval()).toEqual(staleThreshold * ms);
+
+ monitor.reconnectAttempts = 1;
+ expect(monitor.getPollInterval()).toEqual(staleThreshold * backoffFactor * ms);
+
+ monitor.reconnectAttempts = 2;
+ expect(monitor.getPollInterval()).toEqual(
+ staleThreshold * backoffFactor * backoffFactor * ms,
+ );
+ });
+
+ it('caps exponential backoff after some number of reconnection attempts', () => {
+ Math.random = () => 0;
+ monitor.reconnectAttempts = 42;
+ const cappedPollInterval = monitor.getPollInterval();
+
+ monitor.reconnectAttempts = 9001;
+ expect(monitor.getPollInterval()).toEqual(cappedPollInterval);
+ });
+
+ it('uses 100% jitter when 0 reconnection attempts', () => {
+ Math.random = () => 0;
+ expect(monitor.getPollInterval()).toEqual(staleThreshold * ms);
+
+ Math.random = () => 0.5;
+ expect(monitor.getPollInterval()).toEqual(staleThreshold * 1.5 * ms);
+ });
+
+ it('uses reconnectionBackoffRate for jitter when >0 reconnection attempts', () => {
+ monitor.reconnectAttempts = 1;
+
+ Math.random = () => 0.25;
+ expect(monitor.getPollInterval()).toEqual(
+ staleThreshold * backoffFactor * (1 + reconnectionBackoffRate * 0.25) * ms,
+ );
+
+ Math.random = () => 0.5;
+ expect(monitor.getPollInterval()).toEqual(
+ staleThreshold * backoffFactor * (1 + reconnectionBackoffRate * 0.5) * ms,
+ );
+ });
+
+ it('applies jitter after capped exponential backoff', () => {
+ monitor.reconnectAttempts = 9001;
+
+ Math.random = () => 0;
+ const withoutJitter = monitor.getPollInterval();
+ Math.random = () => 0.5;
+ const withJitter = monitor.getPollInterval();
+
+ expect(withJitter).toBeGreaterThan(withoutJitter);
+ });
+ });
+});
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 1a3b151afa0..d32e582e498 100644
--- a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
+++ b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
@@ -1,12 +1,12 @@
+import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
import AddReviewItemsModal from '~/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue';
-import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
-import defaultState from '~/add_context_commits_modal/store/state';
-import mutations from '~/add_context_commits_modal/store/mutations';
import * as actions from '~/add_context_commits_modal/store/actions';
+import mutations from '~/add_context_commits_modal/store/mutations';
+import defaultState from '~/add_context_commits_modal/store/state';
+import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
const localVue = createLocalVue();
localVue.use(Vuex);
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 4e65713a680..75f1cc41e23 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
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
import CommitItem from '~/diffs/components/commit_item.vue';
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
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 3bb3cb68f56..fa4d52cbfbb 100644
--- a/spec/frontend/add_context_commits_modal/store/actions_spec.js
+++ b/spec/frontend/add_context_commits_modal/store/actions_spec.js
@@ -1,7 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
import {
setBaseConfig,
setTabIndex,
@@ -16,6 +15,7 @@ import {
resetModalState,
} from '~/add_context_commits_modal/store/actions';
import * as types from '~/add_context_commits_modal/store/mutation_types';
+import axios from '~/lib/utils/axios_utils';
describe('AddContextCommitsModalStoreActions', () => {
const contextCommitEndpoint =
diff --git a/spec/frontend/add_context_commits_modal/store/mutations_spec.js b/spec/frontend/add_context_commits_modal/store/mutations_spec.js
index 22f82570ab1..2331a4af1bc 100644
--- a/spec/frontend/add_context_commits_modal/store/mutations_spec.js
+++ b/spec/frontend/add_context_commits_modal/store/mutations_spec.js
@@ -1,6 +1,6 @@
import { TEST_HOST } from 'helpers/test_constants';
-import mutations from '~/add_context_commits_modal/store/mutations';
import * as types from '~/add_context_commits_modal/store/mutation_types';
+import mutations from '~/add_context_commits_modal/store/mutations';
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
describe('AddContextCommitsModalStoreMutations', () => {
diff --git a/spec/frontend/admin/statistics_panel/components/app_spec.js b/spec/frontend/admin/statistics_panel/components/app_spec.js
index a4dcfa1a480..9c424491d04 100644
--- a/spec/frontend/admin/statistics_panel/components/app_spec.js
+++ b/spec/frontend/admin/statistics_panel/components/app_spec.js
@@ -1,12 +1,12 @@
-import Vuex from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import { GlLoadingIcon } from '@gitlab/ui';
-import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import Vuex from 'vuex';
import StatisticsPanelApp from '~/admin/statistics_panel/components/app.vue';
import statisticsLabels from '~/admin/statistics_panel/constants';
import createStore from '~/admin/statistics_panel/store';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import mockStatistics from '../mock_data';
const localVue = createLocalVue();
diff --git a/spec/frontend/admin/statistics_panel/store/actions_spec.js b/spec/frontend/admin/statistics_panel/store/actions_spec.js
index ecbc823be12..c7481b664b3 100644
--- a/spec/frontend/admin/statistics_panel/store/actions_spec.js
+++ b/spec/frontend/admin/statistics_panel/store/actions_spec.js
@@ -1,10 +1,10 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import * as actions from '~/admin/statistics_panel/store/actions';
import * as types from '~/admin/statistics_panel/store/mutation_types';
import getInitialState from '~/admin/statistics_panel/store/state';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import mockStatistics from '../mock_data';
describe('Admin statistics panel actions', () => {
diff --git a/spec/frontend/admin/statistics_panel/store/getters_spec.js b/spec/frontend/admin/statistics_panel/store/getters_spec.js
index 152d82531ed..6cdd40b1a98 100644
--- a/spec/frontend/admin/statistics_panel/store/getters_spec.js
+++ b/spec/frontend/admin/statistics_panel/store/getters_spec.js
@@ -1,5 +1,5 @@
-import createState from '~/admin/statistics_panel/store/state';
import * as getters from '~/admin/statistics_panel/store/getters';
+import createState from '~/admin/statistics_panel/store/state';
describe('Admin statistics panel getters', () => {
let state;
diff --git a/spec/frontend/admin/statistics_panel/store/mutations_spec.js b/spec/frontend/admin/statistics_panel/store/mutations_spec.js
index 179f38d2bc5..0a3dad09c9a 100644
--- a/spec/frontend/admin/statistics_panel/store/mutations_spec.js
+++ b/spec/frontend/admin/statistics_panel/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/admin/statistics_panel/store/mutations';
import * as types from '~/admin/statistics_panel/store/mutation_types';
+import mutations from '~/admin/statistics_panel/store/mutations';
import getInitialState from '~/admin/statistics_panel/store/state';
import mockStatistics from '../mock_data';
diff --git a/spec/frontend/admin/users/components/actions/actions_spec.js b/spec/frontend/admin/users/components/actions/actions_spec.js
new file mode 100644
index 00000000000..5e232f34311
--- /dev/null
+++ b/spec/frontend/admin/users/components/actions/actions_spec.js
@@ -0,0 +1,98 @@
+import { GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { kebabCase } from 'lodash';
+import { nextTick } from 'vue';
+import Actions from '~/admin/users/components/actions';
+import SharedDeleteAction from '~/admin/users/components/actions/shared/shared_delete_action.vue';
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+import { CONFIRMATION_ACTIONS, DELETE_ACTIONS } from '../../constants';
+
+describe('Action components', () => {
+ let wrapper;
+
+ const findDropdownItem = () => wrapper.find(GlDropdownItem);
+
+ const initComponent = ({ component, props, stubs = {} } = {}) => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ ...props,
+ },
+ stubs,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('CONFIRMATION_ACTIONS', () => {
+ it.each(CONFIRMATION_ACTIONS)('renders a dropdown item for "%s"', async (action) => {
+ initComponent({
+ component: Actions[capitalizeFirstCharacter(action)],
+ props: {
+ username: 'John Doe',
+ path: '/test',
+ },
+ });
+
+ await nextTick();
+
+ const div = wrapper.find('div');
+ expect(div.attributes('data-path')).toBe('/test');
+ expect(div.attributes('data-modal-attributes')).toContain('John Doe');
+ expect(findDropdownItem().exists()).toBe(true);
+ });
+ });
+
+ describe('LINK_ACTIONS', () => {
+ it.each`
+ action | method
+ ${'Approve'} | ${'put'}
+ ${'Reject'} | ${'delete'}
+ `(
+ 'renders a dropdown item link with method "$method" for "$action"',
+ async ({ action, method }) => {
+ initComponent({
+ component: Actions[action],
+ props: {
+ path: '/test',
+ },
+ });
+
+ await nextTick();
+
+ const item = wrapper.find(GlDropdownItem);
+ expect(item.attributes('href')).toBe('/test');
+ expect(item.attributes('data-method')).toContain(method);
+ },
+ );
+ });
+
+ describe('DELETE_ACTION_COMPONENTS', () => {
+ it.each(DELETE_ACTIONS)('renders a dropdown item for "%s"', async (action) => {
+ initComponent({
+ component: Actions[capitalizeFirstCharacter(action)],
+ props: {
+ username: 'John Doe',
+ paths: {
+ delete: '/delete',
+ block: '/block',
+ },
+ },
+ stubs: { SharedDeleteAction },
+ });
+
+ await nextTick();
+
+ const sharedAction = wrapper.find(SharedDeleteAction);
+
+ expect(sharedAction.attributes('data-block-user-url')).toBe('/block');
+ expect(sharedAction.attributes('data-delete-user-url')).toBe('/delete');
+ expect(sharedAction.attributes('data-gl-modal-action')).toBe(kebabCase(action));
+ expect(sharedAction.attributes('data-username')).toBe('John Doe');
+ expect(findDropdownItem().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/components/user_actions_spec.js b/spec/frontend/admin/users/components/user_actions_spec.js
new file mode 100644
index 00000000000..0745d961f25
--- /dev/null
+++ b/spec/frontend/admin/users/components/user_actions_spec.js
@@ -0,0 +1,158 @@
+import { GlDropdownDivider } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Actions from '~/admin/users/components/actions';
+import AdminUserActions from '~/admin/users/components/user_actions.vue';
+import { I18N_USER_ACTIONS } from '~/admin/users/constants';
+import { generateUserPaths } from '~/admin/users/utils';
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+import { CONFIRMATION_ACTIONS, DELETE_ACTIONS, LINK_ACTIONS, LDAP, EDIT } from '../constants';
+import { users, paths } from '../mock_data';
+
+describe('AdminUserActions component', () => {
+ let wrapper;
+ const user = users[0];
+ const userPaths = generateUserPaths(paths, user.username);
+
+ const findEditButton = () => wrapper.find('[data-testid="edit"]');
+ const findActionsDropdown = () => wrapper.find('[data-testid="actions"');
+ const findDropdownDivider = () => wrapper.find(GlDropdownDivider);
+
+ const initComponent = ({ actions = [] } = {}) => {
+ wrapper = shallowMount(AdminUserActions, {
+ propsData: {
+ user: {
+ ...user,
+ actions,
+ },
+ paths,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('edit button', () => {
+ describe('when the user has an edit action attached', () => {
+ beforeEach(() => {
+ initComponent({ actions: [EDIT] });
+ });
+
+ it('renders the edit button linking to the user edit path', () => {
+ expect(findEditButton().exists()).toBe(true);
+ expect(findEditButton().attributes('href')).toBe(userPaths.edit);
+ });
+ });
+
+ describe('when there is no edit action attached to the user', () => {
+ beforeEach(() => {
+ initComponent({ actions: [] });
+ });
+
+ it('does not render the edit button linking to the user edit path', () => {
+ expect(findEditButton().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('actions dropdown', () => {
+ describe('when there are actions', () => {
+ const actions = [EDIT, ...LINK_ACTIONS];
+
+ beforeEach(() => {
+ initComponent({ actions });
+ });
+
+ it('renders the actions dropdown', () => {
+ expect(findActionsDropdown().exists()).toBe(true);
+ });
+
+ describe('when there are actions that should render as links', () => {
+ beforeEach(() => {
+ initComponent({ actions: LINK_ACTIONS });
+ });
+
+ it.each(LINK_ACTIONS)('renders an action component item for "%s"', (action) => {
+ const component = wrapper.find(Actions[capitalizeFirstCharacter(action)]);
+
+ expect(component.props('path')).toBe(userPaths[action]);
+ expect(component.text()).toBe(I18N_USER_ACTIONS[action]);
+ });
+ });
+
+ describe('when there are actions that require confirmation', () => {
+ beforeEach(() => {
+ initComponent({ actions: CONFIRMATION_ACTIONS });
+ });
+
+ it.each(CONFIRMATION_ACTIONS)('renders an action component item for "%s"', (action) => {
+ const component = wrapper.find(Actions[capitalizeFirstCharacter(action)]);
+
+ expect(component.props('username')).toBe(user.name);
+ expect(component.props('path')).toBe(userPaths[action]);
+ expect(component.text()).toBe(I18N_USER_ACTIONS[action]);
+ });
+ });
+
+ describe('when there is a LDAP action', () => {
+ beforeEach(() => {
+ initComponent({ actions: [LDAP] });
+ });
+
+ it('renders the LDAP dropdown item without a link', () => {
+ const dropdownAction = wrapper.find(`[data-testid="${LDAP}"]`);
+ expect(dropdownAction.exists()).toBe(true);
+ expect(dropdownAction.attributes('href')).toBe(undefined);
+ expect(dropdownAction.text()).toBe(I18N_USER_ACTIONS[LDAP]);
+ });
+ });
+
+ describe('when there is a delete action', () => {
+ beforeEach(() => {
+ initComponent({ actions: [LDAP, ...DELETE_ACTIONS] });
+ });
+
+ it('renders a dropdown divider', () => {
+ expect(findDropdownDivider().exists()).toBe(true);
+ });
+
+ it('only renders delete dropdown items for actions containing the word "delete"', () => {
+ const { length } = wrapper.findAll(`[data-testid*="delete-"]`);
+ expect(length).toBe(DELETE_ACTIONS.length);
+ });
+
+ it.each(DELETE_ACTIONS)('renders a delete action component item for "%s"', (action) => {
+ const component = wrapper.find(Actions[capitalizeFirstCharacter(action)]);
+
+ expect(component.props('username')).toBe(user.name);
+ expect(component.props('paths')).toEqual(userPaths);
+ expect(component.text()).toBe(I18N_USER_ACTIONS[action]);
+ });
+ });
+
+ describe('when there are no delete actions', () => {
+ it('does not render a dropdown divider', () => {
+ expect(findDropdownDivider().exists()).toBe(false);
+ });
+
+ it('does not render a delete dropdown item', () => {
+ const anyDeleteAction = wrapper.find(`[data-testid*="delete-"]`);
+ expect(anyDeleteAction.exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('when there are no actions', () => {
+ beforeEach(() => {
+ initComponent({ actions: [] });
+ });
+
+ it('does not render the actions dropdown', () => {
+ expect(findActionsDropdown().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/components/user_avatar_spec.js b/spec/frontend/admin/users/components/user_avatar_spec.js
index ba4e83690d0..8bbfb89bec1 100644
--- a/spec/frontend/admin/users/components/user_avatar_spec.js
+++ b/spec/frontend/admin/users/components/user_avatar_spec.js
@@ -1,7 +1,10 @@
-import { GlAvatarLink, GlAvatarLabeled, GlBadge } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlAvatarLabeled, GlBadge, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import AdminUserAvatar from '~/admin/users/components/user_avatar.vue';
+import { LENGTH_OF_USER_NOTE_TOOLTIP } from '~/admin/users/constants';
+import { truncate } from '~/lib/utils/text_utility';
import { users, paths } from '../mock_data';
describe('AdminUserAvatar component', () => {
@@ -9,17 +12,25 @@ describe('AdminUserAvatar component', () => {
const user = users[0];
const adminUserPath = paths.adminUser;
+ const findNote = () => wrapper.find(GlIcon);
const findAvatar = () => wrapper.find(GlAvatarLabeled);
- const findAvatarLink = () => wrapper.find(GlAvatarLink);
+ const findUserLink = () => wrapper.find('.js-user-link');
const findAllBadges = () => wrapper.findAll(GlBadge);
+ const findTooltip = () => getBinding(findNote().element, 'gl-tooltip');
const initComponent = (props = {}) => {
- wrapper = mount(AdminUserAvatar, {
+ wrapper = shallowMount(AdminUserAvatar, {
propsData: {
user,
adminUserPath,
...props,
},
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ stubs: {
+ GlAvatarLabeled,
+ },
});
};
@@ -33,31 +44,83 @@ describe('AdminUserAvatar component', () => {
initComponent();
});
- it("links to the user's admin path", () => {
- expect(findAvatarLink().attributes()).toMatchObject({
- href: adminUserPath.replace('id', user.username),
+ it('adds a user link hover card', () => {
+ expect(findUserLink().attributes()).toMatchObject({
'data-user-id': user.id.toString(),
'data-username': user.username,
});
});
- it("renders the user's name", () => {
- expect(findAvatar().props('label')).toBe(user.name);
+ it("renders the user's name with an admin path link", () => {
+ const avatar = findAvatar();
+
+ expect(avatar.props('label')).toBe(user.name);
+ expect(avatar.props('labelLink')).toBe(adminUserPath.replace('id', user.username));
});
- it("renders the user's email", () => {
- expect(findAvatar().props('subLabel')).toBe(user.email);
+ it("renders the user's email with a mailto link", () => {
+ const avatar = findAvatar();
+
+ expect(avatar.props('subLabel')).toBe(user.email);
+ expect(avatar.props('subLabelLink')).toBe(`mailto:${user.email}`);
});
it("renders the user's avatar image", () => {
expect(findAvatar().attributes('src')).toBe(user.avatarUrl);
});
+ it('renders a user note icon', () => {
+ expect(findNote().exists()).toBe(true);
+ expect(findNote().props('name')).toBe('document');
+ });
+
+ it("renders the user's note tooltip", () => {
+ const tooltip = findTooltip();
+
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value).toBe(user.note);
+ });
+
it("renders the user's badges", () => {
findAllBadges().wrappers.forEach((badge, idx) => {
expect(badge.text()).toBe(user.badges[idx].text);
expect(badge.props('variant')).toBe(user.badges[idx].variant);
});
});
+
+ describe('and the user note is very long', () => {
+ const noteText = new Array(LENGTH_OF_USER_NOTE_TOOLTIP + 1).join('a');
+
+ beforeEach(() => {
+ initComponent({
+ user: {
+ ...user,
+ note: noteText,
+ },
+ });
+ });
+
+ it("renders a truncated user's note tooltip", () => {
+ const tooltip = findTooltip();
+
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value).toBe(truncate(noteText, LENGTH_OF_USER_NOTE_TOOLTIP));
+ });
+ });
+
+ describe('and the user does not have a note', () => {
+ beforeEach(() => {
+ initComponent({
+ user: {
+ ...user,
+ note: null,
+ },
+ });
+ });
+
+ it('does not render a user note', () => {
+ expect(findNote().exists()).toBe(false);
+ });
+ });
});
});
diff --git a/spec/frontend/admin/users/components/user_date_spec.js b/spec/frontend/admin/users/components/user_date_spec.js
new file mode 100644
index 00000000000..6428b10059b
--- /dev/null
+++ b/spec/frontend/admin/users/components/user_date_spec.js
@@ -0,0 +1,34 @@
+import { shallowMount } from '@vue/test-utils';
+
+import UserDate from '~/admin/users/components/user_date.vue';
+import { users } from '../mock_data';
+
+const mockDate = users[0].createdAt;
+
+describe('FormatDate component', () => {
+ let wrapper;
+
+ const initComponent = (props = {}) => {
+ wrapper = shallowMount(UserDate, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it.each`
+ date | output
+ ${mockDate} | ${'13 Nov, 2020'}
+ ${null} | ${'Never'}
+ ${undefined} | ${'Never'}
+ `('renders $date as $output', ({ date, output }) => {
+ initComponent({ date });
+
+ expect(wrapper.text()).toBe(output);
+ });
+});
diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js
index b79d2d4d39d..f1fcc20fb65 100644
--- a/spec/frontend/admin/users/components/users_table_spec.js
+++ b/spec/frontend/admin/users/components/users_table_spec.js
@@ -1,8 +1,11 @@
import { GlTable } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import AdminUsersTable from '~/admin/users/components/users_table.vue';
+import AdminUserActions from '~/admin/users/components/user_actions.vue';
import AdminUserAvatar from '~/admin/users/components/user_avatar.vue';
+import AdminUserDate from '~/admin/users/components/user_date.vue';
+import AdminUsersTable from '~/admin/users/components/users_table.vue';
+
import { users, paths } from '../mock_data';
describe('AdminUsersTable component', () => {
@@ -39,18 +42,21 @@ describe('AdminUsersTable component', () => {
initComponent();
});
- it.each`
- key | label
- ${'name'} | ${'Name'}
- ${'projectsCount'} | ${'Projects'}
- ${'createdAt'} | ${'Created on'}
- ${'lastActivityOn'} | ${'Last activity'}
- `('renders users.$key in column $label', ({ key, label }) => {
- expect(getCellByLabel(0, label).text()).toContain(`${user[key]}`);
+ it('renders the projects count', () => {
+ expect(getCellByLabel(0, 'Projects').text()).toContain(`${user.projectsCount}`);
});
- it('renders an AdminUserAvatar component', () => {
- expect(getCellByLabel(0, 'Name').find(AdminUserAvatar).exists()).toBe(true);
+ it('renders the user actions', () => {
+ expect(wrapper.find(AdminUserActions).exists()).toBe(true);
+ });
+
+ it.each`
+ component | label
+ ${AdminUserAvatar} | ${'Name'}
+ ${AdminUserDate} | ${'Created on'}
+ ${AdminUserDate} | ${'Last activity'}
+ `('renders the component for column $label', ({ component, label }) => {
+ expect(getCellByLabel(0, label).find(component).exists()).toBe(true);
});
});
diff --git a/spec/frontend/admin/users/constants.js b/spec/frontend/admin/users/constants.js
new file mode 100644
index 00000000000..60abdc6c248
--- /dev/null
+++ b/spec/frontend/admin/users/constants.js
@@ -0,0 +1,19 @@
+const BLOCK = 'block';
+const UNBLOCK = 'unblock';
+const DELETE = 'delete';
+const DELETE_WITH_CONTRIBUTIONS = 'deleteWithContributions';
+const UNLOCK = 'unlock';
+const ACTIVATE = 'activate';
+const DEACTIVATE = 'deactivate';
+const REJECT = 'reject';
+const APPROVE = 'approve';
+
+export const EDIT = 'edit';
+
+export const LDAP = 'ldapBlocked';
+
+export const LINK_ACTIONS = [APPROVE, REJECT];
+
+export const CONFIRMATION_ACTIONS = [ACTIVATE, BLOCK, DEACTIVATE, UNLOCK, UNBLOCK];
+
+export const DELETE_ACTIONS = [DELETE, DELETE_WITH_CONTRIBUTIONS];
diff --git a/spec/frontend/admin/users/index_spec.js b/spec/frontend/admin/users/index_spec.js
index 171d54c8f4f..20b60bd8640 100644
--- a/spec/frontend/admin/users/index_spec.js
+++ b/spec/frontend/admin/users/index_spec.js
@@ -1,5 +1,5 @@
import { createWrapper } from '@vue/test-utils';
-import initAdminUsers from '~/admin/users';
+import { initAdminUsersApp } from '~/admin/users';
import AdminUsersApp from '~/admin/users/components/app.vue';
import { users, paths } from './mock_data';
@@ -16,7 +16,7 @@ describe('initAdminUsersApp', () => {
document.body.appendChild(el);
- wrapper = createWrapper(initAdminUsers(el));
+ wrapper = createWrapper(initAdminUsersApp(el));
});
afterEach(() => {
diff --git a/spec/frontend/admin/users/mock_data.js b/spec/frontend/admin/users/mock_data.js
index 860994a9152..c3918ef5173 100644
--- a/spec/frontend/admin/users/mock_data.js
+++ b/spec/frontend/admin/users/mock_data.js
@@ -14,6 +14,7 @@ export const users = [
],
projectsCount: 0,
actions: [],
+ note: 'Create per issue #999',
},
];
diff --git a/spec/frontend/alert_management/components/alert_management_empty_state_spec.js b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
index 509c67743c1..c2bf90e7635 100644
--- a/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import AlertManagementEmptyState from '~/alert_management/components/alert_management_empty_state.vue';
import defaultProvideValues from '../mocks/alerts_provide_config.json';
diff --git a/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
index 1d79b10a796..bba5fcbbf08 100644
--- a/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import AlertManagementList from '~/alert_management/components/alert_management_list_wrapper.vue';
import AlertManagementEmptyState from '~/alert_management/components/alert_management_empty_state.vue';
+import AlertManagementList from '~/alert_management/components/alert_management_list_wrapper.vue';
import AlertManagementTable from '~/alert_management/components/alert_management_table.vue';
import defaultProvideValues from '../mocks/alerts_provide_config.json';
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 0cc3d565e10..cea665aa50d 100644
--- a/spec/frontend/alert_management/components/alert_management_table_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -1,12 +1,12 @@
-import { mount } from '@vue/test-utils';
import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { visitUrl } from '~/lib/utils/url_utility';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import mockAlerts from 'jest/vue_shared/alert_details/mocks/alerts.json';
import AlertManagementTable from '~/alert_management/components/alert_management_table.vue';
+import { visitUrl } from '~/lib/utils/url_utility';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
-import mockAlerts from '../mocks/alerts.json';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import defaultProvideValues from '../mocks/alerts_provide_config.json';
jest.mock('~/lib/utils/url_utility', () => ({
diff --git a/spec/frontend/alerts_service_settings/components/__snapshots__/alerts_service_form_spec.js.snap b/spec/frontend/alerts_service_settings/components/__snapshots__/alerts_service_form_spec.js.snap
deleted file mode 100644
index 0d4171a20b3..00000000000
--- a/spec/frontend/alerts_service_settings/components/__snapshots__/alerts_service_form_spec.js.snap
+++ /dev/null
@@ -1,9 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`AlertsServiceForm with default values renders "authorization-key" input 1`] = `"<gl-form-input-stub id=\\"authorization-key\\" readonly=\\"true\\" value=\\"abcedfg123\\"></gl-form-input-stub>"`;
-
-exports[`AlertsServiceForm with default values renders "url" input 1`] = `"<gl-form-input-stub id=\\"url\\" readonly=\\"true\\" value=\\"https://gitlab.com/endpoint-url\\"></gl-form-input-stub>"`;
-
-exports[`AlertsServiceForm with default values renders toggle button 1`] = `"<toggle-button-stub id=\\"activated\\"></toggle-button-stub>"`;
-
-exports[`AlertsServiceForm with default values shows description and docs links 1`] = `"<p><gl-sprintf-stub message=\\"You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.\\"></gl-sprintf-stub></p><p><gl-sprintf-stub message=\\"Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.\\"></gl-sprintf-stub></p>"`;
diff --git a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
deleted file mode 100644
index 346059ed7be..00000000000
--- a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import { nextTick } from 'vue';
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import AlertsServiceForm from '~/alerts_service_settings/components/alerts_service_form.vue';
-import ToggleButton from '~/vue_shared/components/toggle_button.vue';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-
-jest.mock('~/flash');
-
-const defaultProps = {
- initialAuthorizationKey: 'abcedfg123',
- formPath: 'http://invalid',
- url: 'https://gitlab.com/endpoint-url',
- alertsSetupUrl: 'http://invalid',
- alertsUsageUrl: 'http://invalid',
- initialActivated: false,
- isDisabled: false,
-};
-
-describe('AlertsServiceForm', () => {
- let wrapper;
- let mockAxios;
-
- const createComponent = (props = defaultProps) => {
- wrapper = shallowMount(AlertsServiceForm, {
- propsData: {
- ...defaultProps,
- ...props,
- },
- });
- };
-
- const findUrl = () => wrapper.find('#url');
- const findAuthorizationKey = () => wrapper.find('#authorization-key');
- const findDescription = () => wrapper.find('[data-testid="description"');
-
- beforeEach(() => {
- mockAxios = new MockAdapter(axios);
- });
-
- afterEach(() => {
- wrapper.destroy();
- mockAxios.restore();
- });
-
- describe('with default values', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders "url" input', () => {
- expect(findUrl().html()).toMatchSnapshot();
- });
-
- it('renders "authorization-key" input', () => {
- expect(findAuthorizationKey().html()).toMatchSnapshot();
- });
-
- it('renders toggle button', () => {
- expect(wrapper.find(ToggleButton).html()).toMatchSnapshot();
- });
-
- it('shows description and docs links', () => {
- expect(findDescription().element.innerHTML).toMatchSnapshot();
- });
- });
-
- describe('reset key', () => {
- it('updates the authorization key on success', async () => {
- const formPath = 'some/path';
- mockAxios.onPut(formPath).replyOnce(200, { token: 'newToken' });
-
- createComponent({ formPath });
-
- wrapper.find(GlModal).vm.$emit('ok');
- await axios.waitForAll();
-
- expect(findAuthorizationKey().attributes('value')).toBe('newToken');
- });
-
- it('shows flash message on error', () => {
- const formPath = 'some/path';
- mockAxios.onPut(formPath).replyOnce(404);
-
- createComponent({ formPath });
-
- return wrapper.vm.resetKey().then(() => {
- expect(findAuthorizationKey().attributes('value')).toBe(
- defaultProps.initialAuthorizationKey,
- );
- expect(createFlash).toHaveBeenCalled();
- });
- });
- });
-
- describe('activate toggle', () => {
- describe('successfully completes', () => {
- describe.each`
- initialActivated | value
- ${false} | ${true}
- ${true} | ${false}
- `(
- 'when initialActivated=$initialActivated and value=$value',
- ({ initialActivated, value }) => {
- beforeEach(() => {
- const formPath = 'some/path';
- mockAxios
- .onPut(formPath, { service: { active: value } })
- .replyOnce(200, { active: value });
- createComponent({ initialActivated, formPath });
-
- return wrapper.vm.toggleActivated(value);
- });
-
- it(`updates toggle button value to ${value}`, () => {
- expect(wrapper.find(ToggleButton).props('value')).toBe(value);
- });
- },
- );
- });
-
- describe('error is encountered', () => {
- beforeEach(() => {
- const formPath = 'some/path';
- mockAxios.onPut(formPath).replyOnce(500);
- });
-
- it('restores previous value', () => {
- createComponent({ initialActivated: false });
-
- return wrapper.vm.toggleActivated(true).then(() => {
- expect(wrapper.find(ToggleButton).props('value')).toBe(false);
- });
- });
- });
- });
-
- describe('form is disabled', () => {
- beforeEach(() => {
- createComponent({ isDisabled: true });
- });
-
- it('cannot be toggled', () => {
- wrapper.find(ToggleButton).vm.$emit('change');
- return nextTick().then(() => {
- expect(wrapper.find(ToggleButton).props('disabledInput')).toBe(true);
- });
- });
- });
-});
diff --git a/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap
deleted file mode 100644
index ef68a6a2c32..00000000000
--- a/spec/frontend/alerts_settings/__snapshots__/alerts_settings_form_spec.js.snap
+++ /dev/null
@@ -1,98 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`AlertsSettingsFormNew with default values renders the initial template 1`] = `
-"<form class=\\"gl-mt-6\\">
- <h5 class=\\"gl-font-lg gl-my-5\\">Add new integrations</h5>
- <div id=\\"integration-type\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"integration-type__BV_label_\\" for=\\"integration-type\\" class=\\"d-block col-form-label\\">1. Select integration type</label>
- <div class=\\"bv-no-focus-ring\\"><select class=\\"gl-form-select mw-100 custom-select\\" id=\\"__BVID__8\\">
- <option value=\\"\\">Select integration type</option>
- <option value=\\"HTTP\\">HTTP Endpoint</option>
- <option value=\\"PROMETHEUS\\">External Prometheus</option>
- </select>
- <!---->
- <!---->
- <!---->
- <!---->
- </div>
- </div>
- <transition-stub css=\\"true\\" enterclass=\\"\\" leaveclass=\\"collapse show\\" entertoclass=\\"collapse show\\" leavetoclass=\\"collapse\\" enteractiveclass=\\"collapsing\\" leaveactiveclass=\\"collapsing\\" class=\\"gl-mt-3\\">
- <div class=\\"collapse\\" style=\\"display: none;\\" id=\\"__BVID__10\\">
- <div>
- <div id=\\"name-integration\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"name-integration__BV_label_\\" for=\\"name-integration\\" class=\\"d-block col-form-label\\">2. Name integration</label>
- <div class=\\"bv-no-focus-ring\\"><input type=\\"text\\" placeholder=\\"Enter integration name\\" class=\\"gl-form-input form-control\\" id=\\"__BVID__15\\">
- <!---->
- <!---->
- <!---->
- </div>
- </div>
- <div id=\\"integration-webhook\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"integration-webhook__BV_label_\\" for=\\"integration-webhook\\" class=\\"d-block col-form-label\\">3. Set up webhook</label>
- <div class=\\"bv-no-focus-ring\\"><span>Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html\\" class=\\"gl-link gl-display-inline-block\\">GitLab documentation</a> to learn more about configuring your endpoint.</span> <label class=\\"gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal\\">
- <div class=\\"gl-toggle-wrapper\\"><span class=\\"gl-toggle-label\\">Active</span>
- <!----> <button aria-label=\\"Active\\" type=\\"button\\" class=\\"gl-toggle\\"><span class=\\"toggle-icon\\"><svg data-testid=\\"close-icon\\" aria-hidden=\\"true\\" class=\\"gl-icon s16\\"><use href=\\"#close\\"></use></svg></span></button></div>
- <!---->
- </label>
- <!---->
- <div class=\\"gl-my-4\\"><span class=\\"gl-font-weight-bold\\">
- Webhook URL
- </span>
- <div id=\\"url\\" readonly=\\"readonly\\">
- <div role=\\"group\\" class=\\"input-group\\">
- <!---->
- <!----> <input id=\\"url\\" type=\\"text\\" readonly=\\"readonly\\" class=\\"gl-form-input form-control\\">
- <div class=\\"input-group-append\\"><button title=\\"Copy\\" data-clipboard-text=\\"\\" aria-label=\\"Copy this value\\" type=\\"button\\" class=\\"btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon\\">
- <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" aria-hidden=\\"true\\" class=\\"gl-button-icon gl-icon s16\\">
- <use href=\\"#copy-to-clipboard\\"></use>
- </svg>
- <!----></button></div>
- <!---->
- </div>
- </div>
- </div>
- <div class=\\"gl-my-4\\"><span class=\\"gl-font-weight-bold\\">
- Authorization key
- </span>
- <div id=\\"authorization-key\\" readonly=\\"readonly\\" class=\\"gl-mb-3\\">
- <div role=\\"group\\" class=\\"input-group\\">
- <!---->
- <!----> <input id=\\"authorization-key\\" type=\\"text\\" readonly=\\"readonly\\" class=\\"gl-form-input form-control\\">
- <div class=\\"input-group-append\\"><button title=\\"Copy\\" data-clipboard-text=\\"\\" aria-label=\\"Copy this value\\" type=\\"button\\" class=\\"btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon\\">
- <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" aria-hidden=\\"true\\" class=\\"gl-button-icon gl-icon s16\\">
- <use href=\\"#copy-to-clipboard\\"></use>
- </svg>
- <!----></button></div>
- <!---->
- </div>
- </div> <button type=\\"button\\" disabled=\\"disabled\\" class=\\"btn btn-default btn-md disabled gl-button\\">
- <!---->
- <!----> <span class=\\"gl-button-text\\">
- Reset Key
- </span></button>
- <!---->
- </div>
- <!---->
- <!---->
- <!---->
- </div>
- </div>
- <div id=\\"test-integration\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"test-integration__BV_label_\\" for=\\"test-integration\\" class=\\"d-block col-form-label\\">4. Sample alert payload (optional)</label>
- <div class=\\"bv-no-focus-ring\\"><span>Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).</span> <textarea id=\\"test-payload\\" disabled=\\"disabled\\" placeholder=\\"{ &quot;events&quot;: [{ &quot;application&quot;: &quot;Name of application&quot; }] }\\" wrap=\\"soft\\" class=\\"gl-form-input gl-form-textarea gl-my-3 form-control is-valid\\" style=\\"resize: none; overflow-y: scroll;\\"></textarea>
- <!---->
- <!---->
- <!---->
- </div>
- </div>
- <!---->
- <!---->
- </div>
- <div class=\\"gl-display-flex gl-justify-content-start gl-py-3\\"><button data-testid=\\"integration-form-submit\\" type=\\"submit\\" class=\\"btn js-no-auto-disable btn-success btn-md gl-button\\">
- <!---->
- <!----> <span class=\\"gl-button-text\\">Save integration
- </span></button> <button data-testid=\\"integration-test-and-submit\\" type=\\"button\\" disabled=\\"disabled\\" class=\\"btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary\\">
- <!---->
- <!----> <span class=\\"gl-button-text\\">Save and test payload</span></button> <button type=\\"reset\\" class=\\"btn js-no-auto-disable btn-default btn-md gl-button\\">
- <!---->
- <!----> <span class=\\"gl-button-text\\">Cancel</span></button></div>
- </div>
- </transition-stub>
-</form>"
-`;
diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
new file mode 100644
index 00000000000..eb2b82a0211
--- /dev/null
+++ b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
@@ -0,0 +1,406 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AlertsSettingsForm with default values renders the initial template 1`] = `
+<form
+ class="gl-mt-6"
+>
+ <h5
+ class="gl-font-lg gl-my-5"
+ >
+ Add new integrations
+ </h5>
+
+ <div
+ class="form-group gl-form-group"
+ id="integration-type"
+ role="group"
+ >
+ <label
+ class="d-block col-form-label"
+ for="integration-type"
+ id="integration-type__BV_label_"
+ >
+ 1. Select integration type
+ </label>
+ <div
+ class="bv-no-focus-ring"
+ >
+ <select
+ class="gl-form-select mw-100 custom-select"
+ id="__BVID__8"
+ >
+ <option
+ value=""
+ >
+ Select integration type
+ </option>
+ <option
+ value="HTTP"
+ >
+ HTTP Endpoint
+ </option>
+ <option
+ value="PROMETHEUS"
+ >
+ External Prometheus
+ </option>
+ </select>
+
+ <!---->
+ <!---->
+ <!---->
+ <!---->
+ </div>
+ </div>
+
+ <transition-stub
+ class="gl-mt-3"
+ css="true"
+ enteractiveclass="collapsing"
+ enterclass=""
+ entertoclass="collapse show"
+ leaveactiveclass="collapsing"
+ leaveclass="collapse show"
+ leavetoclass="collapse"
+ >
+ <div
+ class="collapse"
+ id="__BVID__10"
+ style="display: none;"
+ >
+ <div>
+ <div
+ class="form-group gl-form-group"
+ id="name-integration"
+ role="group"
+ >
+ <label
+ class="d-block col-form-label"
+ for="name-integration"
+ id="name-integration__BV_label_"
+ >
+ 2. Name integration
+ </label>
+ <div
+ class="bv-no-focus-ring"
+ >
+ <input
+ class="gl-form-input form-control"
+ id="__BVID__15"
+ placeholder="Enter integration name"
+ type="text"
+ />
+ <!---->
+ <!---->
+ <!---->
+ </div>
+ </div>
+
+ <div
+ class="form-group gl-form-group"
+ id="integration-webhook"
+ role="group"
+ >
+ <label
+ class="d-block col-form-label"
+ for="integration-webhook"
+ id="integration-webhook__BV_label_"
+ >
+ 3. Set up webhook
+ </label>
+ <div
+ class="bv-no-focus-ring"
+ >
+ <span>
+ Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the
+ <a
+ class="gl-link gl-display-inline-block"
+ href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ GitLab documentation
+ </a>
+ to learn more about configuring your endpoint.
+ </span>
+
+ <label
+ class="gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal"
+ >
+ <span
+ class="gl-toggle-wrapper"
+ >
+ <span
+ class="gl-toggle-label"
+ data-testid="toggle-label"
+ >
+ Active
+ </span>
+
+ <!---->
+
+ <button
+ aria-label="Active"
+ class="gl-toggle"
+ role="switch"
+ type="button"
+ >
+ <span
+ class="toggle-icon"
+ >
+ <svg
+ aria-hidden="true"
+ class="gl-icon s16"
+ data-testid="close-icon"
+ >
+ <use
+ href="#close"
+ />
+ </svg>
+ </span>
+ </button>
+ </span>
+
+ <!---->
+ </label>
+
+ <!---->
+
+ <div
+ class="gl-my-4"
+ >
+ <span
+ class="gl-font-weight-bold"
+ >
+
+ Webhook URL
+
+ </span>
+
+ <div
+ id="url"
+ readonly="readonly"
+ >
+ <div
+ class="input-group"
+ role="group"
+ >
+ <!---->
+ <!---->
+
+ <input
+ class="gl-form-input form-control"
+ id="url"
+ readonly="readonly"
+ type="text"
+ />
+
+ <div
+ class="input-group-append"
+ >
+ <button
+ aria-label="Copy this value"
+ class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon"
+ data-clipboard-text=""
+ title="Copy"
+ type="button"
+ >
+ <!---->
+
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon gl-icon s16"
+ data-testid="copy-to-clipboard-icon"
+ >
+ <use
+ href="#copy-to-clipboard"
+ />
+ </svg>
+
+ <!---->
+ </button>
+ </div>
+ <!---->
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-my-4"
+ >
+ <span
+ class="gl-font-weight-bold"
+ >
+
+ Authorization key
+
+ </span>
+
+ <div
+ class="gl-mb-3"
+ id="authorization-key"
+ readonly="readonly"
+ >
+ <div
+ class="input-group"
+ role="group"
+ >
+ <!---->
+ <!---->
+
+ <input
+ class="gl-form-input form-control"
+ id="authorization-key"
+ readonly="readonly"
+ type="text"
+ />
+
+ <div
+ class="input-group-append"
+ >
+ <button
+ aria-label="Copy this value"
+ class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon"
+ data-clipboard-text=""
+ title="Copy"
+ type="button"
+ >
+ <!---->
+
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon gl-icon s16"
+ data-testid="copy-to-clipboard-icon"
+ >
+ <use
+ href="#copy-to-clipboard"
+ />
+ </svg>
+
+ <!---->
+ </button>
+ </div>
+ <!---->
+ </div>
+ </div>
+
+ <button
+ class="btn btn-default btn-md disabled gl-button"
+ disabled="disabled"
+ type="button"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Reset Key
+
+ </span>
+ </button>
+
+ <!---->
+ </div>
+ <!---->
+ <!---->
+ <!---->
+ </div>
+ </div>
+
+ <div
+ class="form-group gl-form-group"
+ id="test-integration"
+ role="group"
+ >
+ <label
+ class="d-block col-form-label"
+ for="test-integration"
+ id="test-integration__BV_label_"
+ >
+ 4. Sample alert payload (optional)
+ </label>
+ <div
+ class="bv-no-focus-ring"
+ >
+ <span>
+ Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).
+ </span>
+
+ <textarea
+ class="gl-form-input gl-form-textarea gl-my-3 form-control is-valid"
+ disabled="disabled"
+ id="test-payload"
+ placeholder="{ \\"events\\": [{ \\"application\\": \\"Name of application\\" }] }"
+ style="resize: none; overflow-y: scroll;"
+ wrap="soft"
+ />
+ <!---->
+ <!---->
+ <!---->
+ </div>
+ </div>
+
+ <!---->
+
+ <!---->
+ </div>
+
+ <div
+ class="gl-display-flex gl-justify-content-start gl-py-3"
+ >
+ <button
+ class="btn js-no-auto-disable btn-success btn-md gl-button"
+ data-testid="integration-form-submit"
+ type="submit"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Save integration
+
+ </span>
+ </button>
+
+ <button
+ class="btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary"
+ data-testid="integration-test-and-submit"
+ disabled="disabled"
+ type="button"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Save and test payload
+ </span>
+ </button>
+
+ <button
+ class="btn js-no-auto-disable btn-default btn-md gl-button"
+ type="reset"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Cancel
+ </span>
+ </button>
+ </div>
+ </div>
+ </transition-stub>
+</form>
+`;
diff --git a/spec/frontend/alerts_settings/alert_mapping_builder_spec.js b/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js
index 5d48ff02e35..7e1d1acb62c 100644
--- a/spec/frontend/alerts_settings/alert_mapping_builder_spec.js
+++ b/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js
@@ -1,8 +1,10 @@
import { GlIcon, GlFormInput, GlDropdown, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AlertMappingBuilder, { i18n } from '~/alerts_settings/components/alert_mapping_builder.vue';
-import gitlabFields from '~/alerts_settings/components/mocks/gitlabFields.json';
import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json';
+import * as transformationUtils from '~/alerts_settings/utils/mapping_transformations';
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+import alertFields from '../mocks/alertFields.json';
describe('AlertMappingBuilder', () => {
let wrapper;
@@ -10,8 +12,9 @@ describe('AlertMappingBuilder', () => {
function mountComponent() {
wrapper = shallowMount(AlertMappingBuilder, {
propsData: {
- payloadFields: parsedMapping.samplePayload.payloadAlerFields.nodes,
- mapping: parsedMapping.storedMapping.nodes,
+ parsedPayload: parsedMapping.samplePayload.payloadAlerFields.nodes,
+ savedMapping: parsedMapping.storedMapping.nodes,
+ alertFields,
},
});
}
@@ -42,52 +45,58 @@ describe('AlertMappingBuilder', () => {
});
it('renders disabled form input for each mapped field', () => {
- gitlabFields.forEach((field, index) => {
+ alertFields.forEach((field, index) => {
const input = findColumnInRow(index + 1, 0).find(GlFormInput);
- expect(input.attributes('value')).toBe(`${field.label} (${field.type.join(' or ')})`);
+ const types = field.types.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(' or ');
+ expect(input.attributes('value')).toBe(`${field.label} (${types})`);
expect(input.attributes('disabled')).toBe('');
});
});
it('renders right arrow next to each input', () => {
- gitlabFields.forEach((field, index) => {
+ alertFields.forEach((field, index) => {
const arrow = findColumnInRow(index + 1, 1).find('.right-arrow');
expect(arrow.exists()).toBe(true);
});
});
it('renders mapping dropdown for each field', () => {
- gitlabFields.forEach(({ compatibleTypes }, index) => {
+ alertFields.forEach(({ types }, index) => {
const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown);
- const searchBox = dropdown.find(GlSearchBoxByType);
- const dropdownItems = dropdown.findAll(GlDropdownItem);
+ const searchBox = dropdown.findComponent(GlSearchBoxByType);
+ const dropdownItems = dropdown.findAllComponents(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
- const numberOfMappingOptions = nodes.filter(({ type }) =>
- type.some((t) => compatibleTypes.includes(t)),
- );
+ const mappingOptions = nodes.filter(({ type }) => types.includes(type));
expect(dropdown.exists()).toBe(true);
expect(searchBox.exists()).toBe(true);
- expect(dropdownItems).toHaveLength(numberOfMappingOptions.length);
+ expect(dropdownItems).toHaveLength(mappingOptions.length);
});
});
it('renders fallback dropdown only for the fields that have fallback', () => {
- gitlabFields.forEach(({ compatibleTypes, numberOfFallbacks }, index) => {
+ alertFields.forEach(({ types, numberOfFallbacks }, index) => {
const dropdown = findColumnInRow(index + 1, 3).find(GlDropdown);
expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks));
if (numberOfFallbacks) {
- const searchBox = dropdown.find(GlSearchBoxByType);
- const dropdownItems = dropdown.findAll(GlDropdownItem);
+ const searchBox = dropdown.findComponent(GlSearchBoxByType);
+ const dropdownItems = dropdown.findAllComponents(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
- const numberOfMappingOptions = nodes.filter(({ type }) =>
- type.some((t) => compatibleTypes.includes(t)),
- );
+ const mappingOptions = nodes.filter(({ type }) => types.includes(type));
expect(searchBox.exists()).toBe(Boolean(numberOfFallbacks));
- expect(dropdownItems).toHaveLength(numberOfMappingOptions.length);
+ expect(dropdownItems).toHaveLength(mappingOptions.length);
}
});
});
+
+ it('emits event with selected mapping', () => {
+ const mappingToSave = { fieldName: 'TITLE', mapping: 'PARSED_TITLE' };
+ jest.spyOn(transformationUtils, 'transformForSave').mockReturnValue(mappingToSave);
+ const dropdown = findColumnInRow(1, 2).find(GlDropdown);
+ const option = dropdown.find(GlDropdownItem);
+ option.vm.$emit('click');
+ expect(wrapper.emitted('onMappingUpdate')[0]).toEqual([mappingToSave]);
+ });
});
diff --git a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js b/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js
index 5a3874d055b..c43d78a1cf3 100644
--- a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js
@@ -1,11 +1,11 @@
import { GlTable, GlIcon, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
-import Tracking from '~/tracking';
import AlertIntegrationsList, {
i18n,
} from '~/alerts_settings/components/alerts_integrations_list.vue';
import { trackAlertIntegrationsViewsOptions } from '~/alerts_settings/constants';
+import Tracking from '~/tracking';
const mockIntegrations = [
{
diff --git a/spec/frontend/alerts_settings/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
index 21cdec6f94c..02229b3d3da 100644
--- a/spec/frontend/alerts_settings/alerts_settings_form_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
@@ -1,4 +1,3 @@
-import { mount } from '@vue/test-utils';
import {
GlForm,
GlFormSelect,
@@ -7,12 +6,15 @@ import {
GlToggle,
GlFormTextarea,
} from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
+import MappingBuilder from '~/alerts_settings/components/alert_mapping_builder.vue';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
-import { defaultAlertSettingsConfig } from './util';
import { typeSet } from '~/alerts_settings/constants';
+import alertFields from '../mocks/alertFields.json';
+import { defaultAlertSettingsConfig } from './util';
-describe('AlertsSettingsFormNew', () => {
+describe('AlertsSettingsForm', () => {
let wrapper;
const mockToastShow = jest.fn();
@@ -20,6 +22,7 @@ describe('AlertsSettingsFormNew', () => {
data = {},
props = {},
multipleHttpIntegrationsCustomMapping = false,
+ multiIntegrations = true,
} = {}) => {
wrapper = mount(AlertsSettingsForm, {
data() {
@@ -31,8 +34,9 @@ describe('AlertsSettingsFormNew', () => {
...props,
},
provide: {
- glFeatures: { multipleHttpIntegrationsCustomMapping },
...defaultAlertSettingsConfig,
+ glFeatures: { multipleHttpIntegrationsCustomMapping },
+ multiIntegrations,
},
mocks: {
$toast: {
@@ -49,6 +53,7 @@ describe('AlertsSettingsFormNew', () => {
const findFormToggle = () => wrapper.find(GlToggle);
const findTestPayloadSection = () => wrapper.find(`[id = "test-integration"]`);
const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`);
+ const findMappingBuilder = () => wrapper.findComponent(MappingBuilder);
const findSubmitButton = () => wrapper.find(`[type = "submit"]`);
const findMultiSupportText = () =>
wrapper.find(`[data-testid="multi-integrations-not-supported"]`);
@@ -63,13 +68,23 @@ describe('AlertsSettingsFormNew', () => {
}
});
+ const selectOptionAtIndex = async (index) => {
+ const options = findSelect().findAll('option');
+ await options.at(index).setSelected();
+ };
+
+ const enableIntegration = (index, value) => {
+ findFormFields().at(index).setValue(value);
+ findFormToggle().trigger('click');
+ };
+
describe('with default values', () => {
beforeEach(() => {
createComponent();
});
it('renders the initial template', () => {
- expect(wrapper.html()).toMatchSnapshot();
+ expect(wrapper.element).toMatchSnapshot();
});
it('render the initial form with only an integration type dropdown', () => {
@@ -80,10 +95,7 @@ describe('AlertsSettingsFormNew', () => {
});
it('shows the rest of the form when the dropdown is used', async () => {
- const options = findSelect().findAll('option');
- await options.at(1).setSelected();
-
- await wrapper.vm.$nextTick();
+ await selectOptionAtIndex(1);
expect(findFormFields().at(0).isVisible()).toBe(true);
});
@@ -96,120 +108,132 @@ describe('AlertsSettingsFormNew', () => {
it('disabled the name input when the selected value is prometheus', async () => {
createComponent();
- const options = findSelect().findAll('option');
- await options.at(2).setSelected();
+ await selectOptionAtIndex(2);
expect(findFormFields().at(0).attributes('disabled')).toBe('disabled');
});
});
describe('submitting integration form', () => {
- it('allows for create-new-integration with the correct form values for HTTP', async () => {
- createComponent();
-
- const options = findSelect().findAll('option');
- await options.at(1).setSelected();
-
- await findFormFields().at(0).setValue('Test integration');
- await findFormToggle().trigger('click');
+ describe('HTTP', () => {
+ it('create', async () => {
+ createComponent();
- await wrapper.vm.$nextTick();
-
- expect(findSubmitButton().exists()).toBe(true);
- expect(findSubmitButton().text()).toBe('Save integration');
+ const integrationName = 'Test integration';
+ await selectOptionAtIndex(1);
+ enableIntegration(0, integrationName);
- findForm().trigger('submit');
+ const submitBtn = findSubmitButton();
+ expect(submitBtn.exists()).toBe(true);
+ expect(submitBtn.text()).toBe('Save integration');
- await wrapper.vm.$nextTick();
+ findForm().trigger('submit');
- expect(wrapper.emitted('create-new-integration')).toBeTruthy();
- expect(wrapper.emitted('create-new-integration')[0]).toEqual([
- { type: typeSet.http, variables: { name: 'Test integration', active: true } },
- ]);
- });
+ expect(wrapper.emitted('create-new-integration')[0]).toEqual([
+ { type: typeSet.http, variables: { name: integrationName, active: true } },
+ ]);
+ });
- it('allows for create-new-integration with the correct form values for PROMETHEUS', async () => {
- createComponent();
+ it('create with custom mapping', async () => {
+ createComponent({
+ multipleHttpIntegrationsCustomMapping: true,
+ multiIntegrations: true,
+ props: { alertFields },
+ });
- const options = findSelect().findAll('option');
- await options.at(2).setSelected();
+ const integrationName = 'Test integration';
+ await selectOptionAtIndex(1);
- await findFormFields().at(0).setValue('Test integration');
- await findFormFields().at(1).setValue('https://test.com');
- await findFormToggle().trigger('click');
+ enableIntegration(0, integrationName);
- await wrapper.vm.$nextTick();
+ const sampleMapping = { field: 'test' };
+ findMappingBuilder().vm.$emit('onMappingUpdate', sampleMapping);
+ findForm().trigger('submit');
- expect(findSubmitButton().exists()).toBe(true);
- expect(findSubmitButton().text()).toBe('Save integration');
+ expect(wrapper.emitted('create-new-integration')[0]).toEqual([
+ {
+ type: typeSet.http,
+ variables: {
+ name: integrationName,
+ active: true,
+ payloadAttributeMappings: sampleMapping,
+ payloadExample: null,
+ },
+ },
+ ]);
+ });
- findForm().trigger('submit');
+ it('update', () => {
+ createComponent({
+ data: {
+ selectedIntegration: typeSet.http,
+ currentIntegration: { id: '1', name: 'Test integration pre' },
+ },
+ props: {
+ loading: false,
+ },
+ });
+ const updatedIntegrationName = 'Test integration post';
+ enableIntegration(0, updatedIntegrationName);
- await wrapper.vm.$nextTick();
+ const submitBtn = findSubmitButton();
+ expect(submitBtn.exists()).toBe(true);
+ expect(submitBtn.text()).toBe('Save integration');
- expect(wrapper.emitted('create-new-integration')).toBeTruthy();
- expect(wrapper.emitted('create-new-integration')[0]).toEqual([
- { type: typeSet.prometheus, variables: { apiUrl: 'https://test.com', active: true } },
- ]);
- });
+ findForm().trigger('submit');
- it('allows for update-integration with the correct form values for HTTP', async () => {
- createComponent({
- data: {
- selectedIntegration: typeSet.http,
- currentIntegration: { id: '1', name: 'Test integration pre' },
- },
- props: {
- loading: false,
- },
+ expect(wrapper.emitted('update-integration')[0]).toEqual([
+ { type: typeSet.http, variables: { name: updatedIntegrationName, active: true } },
+ ]);
});
+ });
- await findFormFields().at(0).setValue('Test integration post');
- await findFormToggle().trigger('click');
+ describe('PROMETHEUS', () => {
+ it('create', async () => {
+ createComponent();
- await wrapper.vm.$nextTick();
+ await selectOptionAtIndex(2);
- expect(findSubmitButton().exists()).toBe(true);
- expect(findSubmitButton().text()).toBe('Save integration');
+ const apiUrl = 'https://test.com';
+ enableIntegration(1, apiUrl);
- findForm().trigger('submit');
+ findFormToggle().trigger('click');
- await wrapper.vm.$nextTick();
+ const submitBtn = findSubmitButton();
+ expect(submitBtn.exists()).toBe(true);
+ expect(submitBtn.text()).toBe('Save integration');
- expect(wrapper.emitted('update-integration')).toBeTruthy();
- expect(wrapper.emitted('update-integration')[0]).toEqual([
- { type: typeSet.http, variables: { name: 'Test integration post', active: true } },
- ]);
- });
+ findForm().trigger('submit');
- it('allows for update-integration with the correct form values for PROMETHEUS', async () => {
- createComponent({
- data: {
- selectedIntegration: typeSet.prometheus,
- currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
- },
- props: {
- loading: false,
- },
+ expect(wrapper.emitted('create-new-integration')[0]).toEqual([
+ { type: typeSet.prometheus, variables: { apiUrl, active: true } },
+ ]);
});
- await findFormFields().at(0).setValue('Test integration');
- await findFormFields().at(1).setValue('https://test-post.com');
- await findFormToggle().trigger('click');
-
- await wrapper.vm.$nextTick();
+ it('update', () => {
+ createComponent({
+ data: {
+ selectedIntegration: typeSet.prometheus,
+ currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
+ },
+ props: {
+ loading: false,
+ },
+ });
- expect(findSubmitButton().exists()).toBe(true);
- expect(findSubmitButton().text()).toBe('Save integration');
+ const apiUrl = 'https://test-post.com';
+ enableIntegration(1, apiUrl);
- findForm().trigger('submit');
+ const submitBtn = findSubmitButton();
+ expect(submitBtn.exists()).toBe(true);
+ expect(submitBtn.text()).toBe('Save integration');
- await wrapper.vm.$nextTick();
+ findForm().trigger('submit');
- expect(wrapper.emitted('update-integration')).toBeTruthy();
- expect(wrapper.emitted('update-integration')[0]).toEqual([
- { type: typeSet.prometheus, variables: { apiUrl: 'https://test-post.com', active: true } },
- ]);
+ expect(wrapper.emitted('update-integration')[0]).toEqual([
+ { type: typeSet.prometheus, variables: { apiUrl, active: true } },
+ ]);
+ });
});
});
@@ -234,9 +258,10 @@ describe('AlertsSettingsFormNew', () => {
jest.runAllTimers();
await wrapper.vm.$nextTick();
- expect(findJsonTestSubmit().exists()).toBe(true);
- expect(findJsonTestSubmit().text()).toBe('Save and test payload');
- expect(findJsonTestSubmit().props('disabled')).toBe(true);
+ const jsonTestSubmit = findJsonTestSubmit();
+ expect(jsonTestSubmit.exists()).toBe(true);
+ expect(jsonTestSubmit.text()).toBe('Save and test payload');
+ expect(jsonTestSubmit.props('disabled')).toBe(true);
});
it('should allow for the form to be automatically saved if the test payload is successfully submitted', async () => {
@@ -257,6 +282,7 @@ describe('AlertsSettingsFormNew', () => {
currentIntegration: {
type: typeSet.http,
},
+ alertFields,
},
});
});
@@ -329,21 +355,29 @@ describe('AlertsSettingsFormNew', () => {
describe('Mapping builder section', () => {
describe.each`
- featureFlag | integrationOption | visible
- ${true} | ${1} | ${true}
- ${true} | ${2} | ${false}
- ${false} | ${1} | ${false}
- ${false} | ${2} | ${false}
- `('', ({ featureFlag, integrationOption, visible }) => {
+ alertFieldsProvided | multiIntegrations | featureFlag | integrationOption | visible
+ ${true} | ${true} | ${true} | ${1} | ${true}
+ ${true} | ${true} | ${true} | ${2} | ${false}
+ ${true} | ${true} | ${false} | ${1} | ${false}
+ ${true} | ${true} | ${false} | ${2} | ${false}
+ ${true} | ${false} | ${true} | ${1} | ${false}
+ ${false} | ${true} | ${true} | ${1} | ${false}
+ `('', ({ alertFieldsProvided, multiIntegrations, featureFlag, integrationOption, visible }) => {
const visibleMsg = visible ? 'is rendered' : 'is not rendered';
const featureFlagMsg = featureFlag ? 'is enabled' : 'is disabled';
+ const alertFieldsMsg = alertFieldsProvided ? 'are provided' : 'are not provided';
const integrationType = integrationOption === 1 ? typeSet.http : typeSet.prometheus;
- it(`${visibleMsg} when multipleHttpIntegrationsCustomMapping feature flag ${featureFlagMsg} and integration type is ${integrationType}`, async () => {
- createComponent({ multipleHttpIntegrationsCustomMapping: featureFlag });
- const options = findSelect().findAll('option');
- options.at(integrationOption).setSelected();
- await wrapper.vm.$nextTick();
+ it(`${visibleMsg} when multipleHttpIntegrationsCustomMapping feature flag ${featureFlagMsg} and integration type is ${integrationType} and alert fields ${alertFieldsMsg}`, async () => {
+ createComponent({
+ multipleHttpIntegrationsCustomMapping: featureFlag,
+ multiIntegrations,
+ props: {
+ alertFields: alertFieldsProvided ? alertFields : [],
+ },
+ });
+ await selectOptionAtIndex(integrationOption);
+
expect(findMappingBuilderSection().exists()).toBe(visible);
});
});
diff --git a/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
index 4d0732ca76c..80293597ab6 100644
--- a/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
@@ -1,23 +1,22 @@
-import VueApollo from 'vue-apollo';
+import { GlLoadingIcon } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
-import { GlLoadingIcon } from '@gitlab/ui';
-import axios from '~/lib/utils/axios_utils';
-import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
-import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
+import waitForPromises from 'helpers/wait_for_promises';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
-import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql';
+import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
+import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
+import { typeSet } from '~/alerts_settings/constants';
import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql';
-import updateHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
-import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '~/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql';
-import { typeSet } from '~/alerts_settings/constants';
+import updateHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
+import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql';
+import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql';
import {
ADD_INTEGRATION_ERROR,
RESET_INTEGRATION_TOKEN_ERROR,
@@ -26,8 +25,7 @@ import {
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
import createFlash from '~/flash';
-import { defaultAlertSettingsConfig } from './util';
-import mockIntegrations from './mocks/integrations.json';
+import axios from '~/lib/utils/axios_utils';
import {
createHttpVariables,
updateHttpVariables,
@@ -40,6 +38,8 @@ import {
integrationToDestroy,
destroyIntegrationResponseWithErrors,
} from './mocks/apollo_mock';
+import mockIntegrations from './mocks/integrations.json';
+import { defaultAlertSettingsConfig } from './util';
jest.mock('~/flash');
diff --git a/spec/frontend/alerts_settings/mocks/apollo_mock.js b/spec/frontend/alerts_settings/components/mocks/apollo_mock.js
index e0eba1e8421..e0eba1e8421 100644
--- a/spec/frontend/alerts_settings/mocks/apollo_mock.js
+++ b/spec/frontend/alerts_settings/components/mocks/apollo_mock.js
diff --git a/spec/frontend/alerts_settings/mocks/integrations.json b/spec/frontend/alerts_settings/components/mocks/integrations.json
index b1284fc55a2..b1284fc55a2 100644
--- a/spec/frontend/alerts_settings/mocks/integrations.json
+++ b/spec/frontend/alerts_settings/components/mocks/integrations.json
diff --git a/spec/frontend/alerts_settings/util.js b/spec/frontend/alerts_settings/components/util.js
index 5c07f22f1c9..5c07f22f1c9 100644
--- a/spec/frontend/alerts_settings/util.js
+++ b/spec/frontend/alerts_settings/components/util.js
diff --git a/spec/frontend/alerts_settings/mocks/alertFields.json b/spec/frontend/alerts_settings/mocks/alertFields.json
new file mode 100644
index 00000000000..ffe59dd0c05
--- /dev/null
+++ b/spec/frontend/alerts_settings/mocks/alertFields.json
@@ -0,0 +1,123 @@
+[
+ {
+ "name": "title",
+ "label": "Title",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ],
+ "numberOfFallbacks": 1
+ },
+ {
+ "name": "description",
+ "label": "Description",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "start_time",
+ "label": "Start time",
+ "type": [
+ "datetime"
+ ],
+ "types": [
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "end_time",
+ "label": "End time",
+ "type": [
+ "datetime"
+ ],
+ "types": [
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "service",
+ "label": "Service",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "monitoring_tool",
+ "label": "Monitoring tool",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "hosts",
+ "label": "Hosts",
+ "type": [
+ "string",
+ "ARRAY"
+ ],
+ "types": [
+ "string",
+ "ARRAY",
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "severity",
+ "label": "Severity",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "fingerprint",
+ "label": "Fingerprint",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ]
+ },
+ {
+ "name": "gitlab_environment_name",
+ "label": "Environment",
+ "type": [
+ "string"
+ ],
+ "types": [
+ "string",
+ "number",
+ "datetime"
+ ]
+ }
+]
diff --git a/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js b/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js
new file mode 100644
index 00000000000..8c1977ffebe
--- /dev/null
+++ b/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js
@@ -0,0 +1,81 @@
+import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json';
+import {
+ getMappingData,
+ getPayloadFields,
+ transformForSave,
+} from '~/alerts_settings/utils/mapping_transformations';
+import alertFields from '../mocks/alertFields.json';
+
+describe('Mapping Transformation Utilities', () => {
+ const nameField = {
+ label: 'Name',
+ path: ['alert', 'name'],
+ type: 'string',
+ };
+ const dashboardField = {
+ label: 'Dashboard Id',
+ path: ['alert', 'dashboardId'],
+ type: 'string',
+ };
+
+ describe('getMappingData', () => {
+ it('should return mapping data', () => {
+ const result = getMappingData(
+ alertFields,
+ getPayloadFields(parsedMapping.samplePayload.payloadAlerFields.nodes.slice(0, 3)),
+ parsedMapping.storedMapping.nodes.slice(0, 3),
+ );
+
+ result.forEach((data, index) => {
+ expect(data).toEqual(
+ expect.objectContaining({
+ ...alertFields[index],
+ searchTerm: '',
+ fallbackSearchTerm: '',
+ }),
+ );
+ });
+ });
+ });
+
+ describe('transformForSave', () => {
+ it('should transform mapped data for save', () => {
+ const fieldName = 'title';
+ const mockMappingData = [
+ {
+ name: fieldName,
+ mapping: 'alert_name',
+ mappingFields: getPayloadFields([dashboardField, nameField]),
+ },
+ ];
+ const result = transformForSave(mockMappingData);
+ const { path, type, label } = nameField;
+ expect(result).toEqual([
+ { fieldName: fieldName.toUpperCase(), path, type: type.toUpperCase(), label },
+ ]);
+ });
+
+ it('should return empty array if no mapping provided', () => {
+ const fieldName = 'title';
+ const mockMappingData = [
+ {
+ name: fieldName,
+ mapping: null,
+ mappingFields: getPayloadFields([nameField, dashboardField]),
+ },
+ ];
+ const result = transformForSave(mockMappingData);
+ expect(result).toEqual([]);
+ });
+ });
+
+ describe('getPayloadFields', () => {
+ it('should add name field to each payload field', () => {
+ const result = getPayloadFields([nameField, dashboardField]);
+ expect(result).toEqual([
+ { ...nameField, name: 'alert_name' },
+ { ...dashboardField, name: 'alert_dashboardId' },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/analytics/components/activity_chart_spec.js b/spec/frontend/analytics/components/activity_chart_spec.js
index 1f0f9a6c5d7..a6b45ffe20f 100644
--- a/spec/frontend/analytics/components/activity_chart_spec.js
+++ b/spec/frontend/analytics/components/activity_chart_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
import ActivityChart from '~/analytics/product_analytics/components/activity_chart.vue';
describe('Activity Chart Bundle', () => {
diff --git a/spec/frontend/analytics/instance_statistics/components/app_spec.js b/spec/frontend/analytics/instance_statistics/components/app_spec.js
index cc676e86e99..b945cc20bd6 100644
--- a/spec/frontend/analytics/instance_statistics/components/app_spec.js
+++ b/spec/frontend/analytics/instance_statistics/components/app_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
-import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue';
import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue';
+import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue';
import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue';
-import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
import ProjectsAndGroupsChart from '~/analytics/instance_statistics/components/projects_and_groups_chart.vue';
+import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
describe('InstanceStatisticsApp', () => {
let wrapper;
diff --git a/spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js b/spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js
index a69f3388cbb..e80dcdff426 100644
--- a/spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js
+++ b/spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js
@@ -1,13 +1,13 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
+import { GlLineChart } from '@gitlab/ui/dist/charts';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue';
import statsQuery from '~/analytics/instance_statistics/graphql/queries/instance_count.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
-import { mockCountsData1 } from '../mock_data';
import { mockQueryResponse, mockApolloResponse } from '../apollo_mock_data';
+import { mockCountsData1 } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
diff --git a/spec/frontend/analytics/instance_statistics/components/projects_and_groups_chart_spec.js b/spec/frontend/analytics/instance_statistics/components/projects_and_groups_chart_spec.js
index bf94e476ea3..bbfc65f19b1 100644
--- a/spec/frontend/analytics/instance_statistics/components/projects_and_groups_chart_spec.js
+++ b/spec/frontend/analytics/instance_statistics/components/projects_and_groups_chart_spec.js
@@ -1,15 +1,14 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
+import { GlLineChart } from '@gitlab/ui/dist/charts';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { useFakeDate } from 'helpers/fake_date';
import ProjectsAndGroupChart from '~/analytics/instance_statistics/components/projects_and_groups_chart.vue';
-import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
-import projectsQuery from '~/analytics/instance_statistics/graphql/queries/projects.query.graphql';
import groupsQuery from '~/analytics/instance_statistics/graphql/queries/groups.query.graphql';
-import { mockCountsData2, roundedSortedCountsMonthlyChartData2 } from '../mock_data';
+import projectsQuery from '~/analytics/instance_statistics/graphql/queries/projects.query.graphql';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockQueryResponse } from '../apollo_mock_data';
+import { mockCountsData2, roundedSortedCountsMonthlyChartData2 } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
@@ -45,8 +44,8 @@ describe('ProjectsAndGroupChart', () => {
return shallowMount(ProjectsAndGroupChart, {
props: {
- startDate: useFakeDate(2020, 9, 26),
- endDate: useFakeDate(2020, 10, 1),
+ startDate: new Date(2020, 9, 26),
+ endDate: new Date(2020, 10, 1),
totalDataPoints: mockCountsData2.length,
},
localVue,
diff --git a/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js b/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js
index b9fa30643df..d857b7fae61 100644
--- a/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js
+++ b/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js
@@ -1,18 +1,17 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
+import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { useFakeDate } from 'helpers/fake_date';
import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
-import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import usersQuery from '~/analytics/instance_statistics/graphql/queries/users.query.graphql';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { mockQueryResponse } from '../apollo_mock_data';
import {
mockCountsData1,
mockCountsData2,
roundedSortedCountsMonthlyChartData2,
} from '../mock_data';
-import { mockQueryResponse } from '../apollo_mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
@@ -31,8 +30,8 @@ describe('UsersChart', () => {
return shallowMount(UsersChart, {
props: {
- startDate: useFakeDate(2020, 9, 26),
- endDate: useFakeDate(2020, 10, 1),
+ startDate: new Date(2020, 9, 26),
+ endDate: new Date(2020, 10, 1),
totalDataPoints: mockCountsData2.length,
},
localVue,
diff --git a/spec/frontend/analytics/shared/components/metric_card_spec.js b/spec/frontend/analytics/shared/components/metric_card_spec.js
index e89d499ed9b..7f587d227ab 100644
--- a/spec/frontend/analytics/shared/components/metric_card_spec.js
+++ b/spec/frontend/analytics/shared/components/metric_card_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
diff --git a/spec/frontend/api/api_utils_spec.js b/spec/frontend/api/api_utils_spec.js
index 3fec26f0149..04be442ef71 100644
--- a/spec/frontend/api/api_utils_spec.js
+++ b/spec/frontend/api/api_utils_spec.js
@@ -20,6 +20,10 @@ describe('~/api/api_utils.js', () => {
);
});
+ it('ensures the URL is prefixed with a /', () => {
+ expect(apiUtils.buildApiUrl('api/:version/projects/:id')).toEqual('/api/v7/projects/:id');
+ });
+
describe('when gon includes a relative_url_root property', () => {
beforeEach(() => {
window.gon.relative_url_root = '/relative/root';
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 76d67195499..d2522a0124a 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
+import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
describe('Api', () => {
@@ -260,6 +260,28 @@ describe('Api', () => {
});
});
+ describe('groupLabels', () => {
+ it('fetches group labels', (done) => {
+ const options = { params: { search: 'foo' } };
+ const expectedGroup = 'gitlab-org';
+ const expectedUrl = `${dummyUrlRoot}/groups/${expectedGroup}/-/labels`;
+ mock.onGet(expectedUrl).reply(httpStatus.OK, [
+ {
+ id: 1,
+ title: 'Foo Label',
+ },
+ ]);
+
+ Api.groupLabels(expectedGroup, options)
+ .then((res) => {
+ expect(res.length).toBe(1);
+ expect(res[0].title).toBe('Foo Label');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('namespaces', () => {
it('fetches namespaces', (done) => {
const query = 'dummy query';
diff --git a/spec/frontend/artifacts_settings/components/__snapshots__/keep_latest_artifact_checkbox_spec.js.snap b/spec/frontend/artifacts_settings/components/__snapshots__/keep_latest_artifact_checkbox_spec.js.snap
index 9d05e6d99f6..bfe7e40fb32 100644
--- a/spec/frontend/artifacts_settings/components/__snapshots__/keep_latest_artifact_checkbox_spec.js.snap
+++ b/spec/frontend/artifacts_settings/components/__snapshots__/keep_latest_artifact_checkbox_spec.js.snap
@@ -1,29 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Keep latest artifact checkbox sets correct setting value in checkbox with query result 1`] = `
+exports[`Keep latest artifact checkbox when application keep latest artifact setting is disabled checkbox is disabled when application setting is disabled 1`] = `
<div>
<!---->
- <gl-form-checkbox-stub
+ <b-form-checkbox-stub
checked="true"
+ class="gl-form-checkbox"
+ disabled="true"
+ plain="true"
+ value="true"
>
- <b
+ <strong
class="gl-mr-3"
>
Keep artifacts from most recent successful jobs
- </b>
+ </strong>
<gl-link-stub
href="/help/ci/pipelines/job_artifacts"
>
More information
</gl-link-stub>
- </gl-form-checkbox-stub>
+
+ <p
+ class="help-text"
+ >
+ This feature is disabled at the instance level.
+ </p>
+ </b-form-checkbox-stub>
+</div>
+`;
+
+exports[`Keep latest artifact checkbox when application keep latest artifact setting is enabled sets correct setting value in checkbox with query result 1`] = `
+<div>
+ <!---->
- <p>
-
- The latest artifacts created by jobs in the most recent successful pipeline will be stored.
-
- </p>
+ <b-form-checkbox-stub
+ checked="true"
+ class="gl-form-checkbox"
+ plain="true"
+ value="true"
+ >
+ <strong
+ class="gl-mr-3"
+ >
+ Keep artifacts from most recent successful jobs
+ </strong>
+
+ <gl-link-stub
+ href="/help/ci/pipelines/job_artifacts"
+ >
+ More information
+ </gl-link-stub>
+
+ <p
+ class="help-text"
+ >
+ The latest artifacts created by jobs in the most recent successful pipeline will be stored.
+ </p>
+ </b-form-checkbox-stub>
</div>
`;
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 d7f07526b58..fe2886d6c95 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
@@ -2,14 +2,15 @@ import { GlFormCheckbox, GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
-import GetKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
import UpdateKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
+import GetKeepLatestArtifactApplicationSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_application_setting.query.graphql';
+import GetKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
+import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
const localVue = createLocalVue();
localVue.use(VueApollo);
-const keepLatestArtifactMock = {
+const keepLatestArtifactProjectMock = {
data: {
project: {
ciCdSettings: { keepLatestArtifact: true },
@@ -17,6 +18,14 @@ const keepLatestArtifactMock = {
},
};
+const keepLatestArtifactApplicationMock = {
+ data: {
+ ciApplicationSettings: {
+ keepLatestArtifact: true,
+ },
+ },
+};
+
const keepLatestArtifactMockResponse = {
data: { ciCdSettingsUpdate: { errors: [], __typename: 'CiCdSettingsUpdatePayload' } },
};
@@ -34,7 +43,12 @@ describe('Keep latest artifact checkbox', () => {
const createComponent = (handlers) => {
requestHandlers = {
- keepLatestArtifactQueryHandler: jest.fn().mockResolvedValue(keepLatestArtifactMock),
+ keepLatestArtifactProjectQueryHandler: jest
+ .fn()
+ .mockResolvedValue(keepLatestArtifactProjectMock),
+ keepLatestArtifactApplicationQueryHandler: jest
+ .fn()
+ .mockResolvedValue(keepLatestArtifactApplicationMock),
keepLatestArtifactMutationHandler: jest
.fn()
.mockResolvedValue(keepLatestArtifactMockResponse),
@@ -42,7 +56,11 @@ describe('Keep latest artifact checkbox', () => {
};
apolloProvider = createMockApollo([
- [GetKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactQueryHandler],
+ [GetKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactProjectQueryHandler],
+ [
+ GetKeepLatestArtifactApplicationSetting,
+ requestHandlers.keepLatestArtifactApplicationQueryHandler,
+ ],
[UpdateKeepLatestArtifactProjectSetting, requestHandlers.keepLatestArtifactMutationHandler],
]);
@@ -51,38 +69,74 @@ describe('Keep latest artifact checkbox', () => {
fullPath,
helpPagePath,
},
+ stubs: {
+ GlFormCheckbox,
+ },
localVue,
apolloProvider,
});
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
wrapper = null;
apolloProvider = null;
});
- it('displays the checkbox and the help link', () => {
- expect(findCheckbox().exists()).toBe(true);
- expect(findHelpLink().exists()).toBe(true);
- });
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('displays the checkbox and the help link', () => {
+ expect(findCheckbox().exists()).toBe(true);
+ expect(findHelpLink().exists()).toBe(true);
+ });
- it('sets correct setting value in checkbox with query result', async () => {
- await wrapper.vm.$nextTick();
+ it('calls mutation on artifact setting change with correct payload', () => {
+ findCheckbox().vm.$emit('change', false);
- expect(wrapper.element).toMatchSnapshot();
+ expect(requestHandlers.keepLatestArtifactMutationHandler).toHaveBeenCalledWith({
+ fullPath,
+ keepLatestArtifact: false,
+ });
+ });
});
- it('calls mutation on artifact setting change with correct payload', () => {
- findCheckbox().vm.$emit('change', false);
+ describe('when application keep latest artifact setting is enabled', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('sets correct setting value in checkbox with query result', async () => {
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('checkbox is enabled when application setting is enabled', async () => {
+ await wrapper.vm.$nextTick();
+
+ expect(findCheckbox().attributes('disabled')).toBeUndefined();
+ });
+ });
- expect(requestHandlers.keepLatestArtifactMutationHandler).toHaveBeenCalledWith({
- fullPath,
- keepLatestArtifact: false,
+ describe('when application keep latest artifact setting is disabled', () => {
+ it('checkbox is disabled when application setting is disabled', async () => {
+ createComponent({
+ keepLatestArtifactApplicationQueryHandler: jest.fn().mockResolvedValue({
+ data: {
+ ciApplicationSettings: {
+ keepLatestArtifact: false,
+ },
+ },
+ }),
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.element).toMatchSnapshot();
+ expect(findCheckbox().attributes('disabled')).toBe('true');
});
});
});
diff --git a/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js b/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js
index 98c8ed4b95d..b77def195b6 100644
--- a/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js
+++ b/spec/frontend/authentication/two_factor_auth/components/recovery_codes_spec.js
@@ -1,17 +1,17 @@
-import { mount } from '@vue/test-utils';
import { GlAlert, GlButton } from '@gitlab/ui';
-import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import Tracking from '~/tracking';
import RecoveryCodes, {
i18n,
} from '~/authentication/two_factor_auth/components/recovery_codes.vue';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import {
RECOVERY_CODE_DOWNLOAD_FILENAME,
COPY_KEYBOARD_SHORTCUT,
} from '~/authentication/two_factor_auth/constants';
+import Tracking from '~/tracking';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { codes, codesFormattedString, codesDownloadHref, profileAccountPath } from '../mock_data';
describe('RecoveryCodes', () => {
diff --git a/spec/frontend/authentication/two_factor_auth/index_spec.js b/spec/frontend/authentication/two_factor_auth/index_spec.js
index b181170b0a1..f5345139021 100644
--- a/spec/frontend/authentication/two_factor_auth/index_spec.js
+++ b/spec/frontend/authentication/two_factor_auth/index_spec.js
@@ -1,8 +1,8 @@
-import { createWrapper } from '@vue/test-utils';
import { getByTestId, fireEvent } from '@testing-library/dom';
-import * as urlUtils from '~/lib/utils/url_utility';
+import { createWrapper } from '@vue/test-utils';
import { initRecoveryCodes, initClose2faSuccessMessage } from '~/authentication/two_factor_auth';
import RecoveryCodes from '~/authentication/two_factor_auth/components/recovery_codes.vue';
+import * as urlUtils from '~/lib/utils/url_utility';
import { codesJsonString, codes, profileAccountPath } from './mock_data';
describe('initRecoveryCodes', () => {
diff --git a/spec/frontend/avatar_helper_spec.js b/spec/frontend/avatar_helper_spec.js
index c4da7189751..91bf8e28774 100644
--- a/spec/frontend/avatar_helper_spec.js
+++ b/spec/frontend/avatar_helper_spec.js
@@ -1,5 +1,4 @@
import { TEST_HOST } from 'spec/test_constants';
-import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
import {
DEFAULT_SIZE_CLASS,
IDENTICON_BG_COUNT,
@@ -8,6 +7,7 @@ import {
getIdenticonBackgroundClass,
getIdenticonTitle,
} from '~/helpers/avatar_helper';
+import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
function matchAll(str) {
return new RegExp(`^${str}$`);
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index e9482ffbd3d..edd17cfd810 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -1,10 +1,10 @@
+import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import Cookies from 'js-cookie';
-import MockAdapter from 'axios-mock-adapter';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
-import axios from '~/lib/utils/axios_utils';
import loadAwardsHandler from '~/awards_handler';
import { EMOJI_VERSION } from '~/emoji';
+import axios from '~/lib/utils/axios_utils';
window.gl = window.gl || {};
window.gon = window.gon || {};
@@ -53,6 +53,12 @@ describe('AwardsHandler', () => {
d: 'smiling face with sunglasses',
u: '6.0',
},
+ grey_question: {
+ c: 'symbols',
+ e: '❔',
+ d: 'white question mark ornament',
+ u: '6.0',
+ },
};
preloadFixtures('snippets/show.html');
@@ -285,16 +291,6 @@ describe('AwardsHandler', () => {
expect($('.js-emoji-menu-search').val()).toBe('');
});
- it('should fuzzy filter the emoji', async () => {
- await openAndWaitForEmojiMenu();
-
- awardsHandler.searchEmojis('sgls');
-
- expect($('[data-name=angel]').is(':visible')).toBe(false);
- expect($('[data-name=anger]').is(':visible')).toBe(false);
- expect($('[data-name=sunglasses]').is(':visible')).toBe(true);
- });
-
it('should filter by emoji description', async () => {
await openAndWaitForEmojiMenu();
diff --git a/spec/frontend/badges/components/badge_form_spec.js b/spec/frontend/badges/components/badge_form_spec.js
index 34c65d51115..e375fcb4705 100644
--- a/spec/frontend/badges/components/badge_form_spec.js
+++ b/spec/frontend/badges/components/badge_form_spec.js
@@ -1,11 +1,11 @@
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
+import Vue from 'vue';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
-import store from '~/badges/store';
-import createEmptyBadge from '~/badges/empty_badge';
+import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeForm from '~/badges/components/badge_form.vue';
+import createEmptyBadge from '~/badges/empty_badge';
+import store from '~/badges/store';
+import axios from '~/lib/utils/axios_utils';
// avoid preview background process
BadgeForm.methods.debouncedPreview = () => {};
diff --git a/spec/frontend/badges/components/badge_list_row_spec.js b/spec/frontend/badges/components/badge_list_row_spec.js
index c559c979cb3..372663017e2 100644
--- a/spec/frontend/badges/components/badge_list_row_spec.js
+++ b/spec/frontend/badges/components/badge_list_row_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
+import BadgeListRow from '~/badges/components/badge_list_row.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
import store from '~/badges/store';
-import BadgeListRow from '~/badges/components/badge_list_row.vue';
import { createDummyBadge } from '../dummy_badge';
describe('BadgeListRow component', () => {
diff --git a/spec/frontend/badges/components/badge_list_spec.js b/spec/frontend/badges/components/badge_list_spec.js
index da59e13e1e9..6cc90c6de46 100644
--- a/spec/frontend/badges/components/badge_list_spec.js
+++ b/spec/frontend/badges/components/badge_list_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
+import BadgeList from '~/badges/components/badge_list.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
import store from '~/badges/store';
-import BadgeList from '~/badges/components/badge_list.vue';
import { createDummyBadge } from '../dummy_badge';
describe('BadgeList component', () => {
diff --git a/spec/frontend/badges/components/badge_settings_spec.js b/spec/frontend/badges/components/badge_settings_spec.js
index 769be7cb1bd..0c29379763e 100644
--- a/spec/frontend/badges/components/badge_settings_spec.js
+++ b/spec/frontend/badges/components/badge_settings_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
-import store from '~/badges/store';
-import BadgeSettings from '~/badges/components/badge_settings.vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import BadgeList from '~/badges/components/badge_list.vue';
import BadgeListRow from '~/badges/components/badge_list_row.vue';
+import BadgeSettings from '~/badges/components/badge_settings.vue';
+import store from '~/badges/store';
import { createDummyBadge } from '../dummy_badge';
const localVue = createLocalVue();
diff --git a/spec/frontend/badges/store/actions_spec.js b/spec/frontend/badges/store/actions_spec.js
index 52ae29affac..75699f24463 100644
--- a/spec/frontend/badges/store/actions_spec.js
+++ b/spec/frontend/badges/store/actions_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'spec/test_constants';
import actions, { transformBackendBadge } from '~/badges/store/actions';
import mutationTypes from '~/badges/store/mutation_types';
import createState from '~/badges/store/state';
+import axios from '~/lib/utils/axios_utils';
import { createDummyBadge, createDummyBadgeResponse } from '../dummy_badge';
describe('Badges store actions', () => {
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index ae7134b63c8..c2d488a465e 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getByRole } from '@testing-library/dom';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import { createStore } from '~/batch_comments/stores';
import NoteableNote from '~/notes/components/noteable_note.vue';
@@ -21,14 +21,11 @@ describe('Batch comments draft note component', () => {
const getList = () => getByRole(wrapper.element, 'list');
- const createComponent = (propsData = { draft }, features = {}) => {
+ const createComponent = (propsData = { draft }) => {
wrapper = shallowMount(localVue.extend(DraftNote), {
store,
propsData,
localVue,
- provide: {
- glFeatures: { multilineComments: true, ...features },
- },
});
jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation();
@@ -145,16 +142,14 @@ describe('Batch comments draft note component', () => {
describe('multiline comments', () => {
describe.each`
- desc | props | features | event | expectedCalls
- ${'with `draft.position`'} | ${draftWithLineRange} | ${{}} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]}
- ${'with `draft.position`'} | ${draftWithLineRange} | ${{}} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]}
- ${'with `draft.position`'} | ${draftWithLineRange} | ${{ multilineComments: false }} | ${'mouseenter'} | ${[]}
- ${'with `draft.position`'} | ${draftWithLineRange} | ${{ multilineComments: false }} | ${'mouseleave'} | ${[]}
- ${'without `draft.position`'} | ${{}} | ${{}} | ${'mouseenter'} | ${[]}
- ${'without `draft.position`'} | ${{}} | ${{}} | ${'mouseleave'} | ${[]}
- `('$desc and features $features', ({ props, event, features, expectedCalls }) => {
+ desc | props | event | expectedCalls
+ ${'with `draft.position`'} | ${draftWithLineRange} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]}
+ ${'with `draft.position`'} | ${draftWithLineRange} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]}
+ ${'without `draft.position`'} | ${{}} | ${'mouseenter'} | ${[]}
+ ${'without `draft.position`'} | ${{}} | ${'mouseleave'} | ${[]}
+ `('$desc', ({ props, event, expectedCalls }) => {
beforeEach(() => {
- createComponent({ draft: { ...draft, ...props } }, features);
+ createComponent({ draft: { ...draft, ...props } });
jest.spyOn(store, 'dispatch');
});
diff --git a/spec/frontend/batch_comments/components/preview_item_spec.js b/spec/frontend/batch_comments/components/preview_item_spec.js
index 173b2710a30..08167a94068 100644
--- a/spec/frontend/batch_comments/components/preview_item_spec.js
+++ b/spec/frontend/batch_comments/components/preview_item_spec.js
@@ -56,17 +56,30 @@ describe('Batch comments draft preview item component', () => {
createComponent(false, {
file_path: 'index.js',
file_hash: 'abc',
- position: { new_line: 1 },
+ position: {
+ line_range: {
+ start: {
+ new_line: 1,
+ type: 'new',
+ },
+ },
+ },
});
- expect(vm.$el.querySelector('.bold').textContent).toContain(':1');
+ expect(vm.$el.querySelector('.bold').textContent).toContain(':+1');
});
it('renders old line position', () => {
createComponent(false, {
file_path: 'index.js',
file_hash: 'abc',
- position: { old_line: 2 },
+ position: {
+ line_range: {
+ start: {
+ old_line: 2,
+ },
+ },
+ },
});
expect(vm.$el.querySelector('.bold').textContent).toContain(':2');
diff --git a/spec/frontend/batch_comments/components/publish_dropdown_spec.js b/spec/frontend/batch_comments/components/publish_dropdown_spec.js
index f235867f002..bd8091c20e0 100644
--- a/spec/frontend/batch_comments/components/publish_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/publish_dropdown_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
import { createStore } from '~/mr_notes/stores';
import '~/behaviors/markdown/render_gfm';
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 3ad131de24e..885e02ef60f 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
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/batch_comments/stores/modules/batch_comments/actions';
import axios from '~/lib/utils/axios_utils';
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 1406f66fd10..fe01de638c2 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
@@ -1,6 +1,6 @@
-import createState from '~/batch_comments/stores/modules/batch_comments/state';
-import mutations from '~/batch_comments/stores/modules/batch_comments/mutations';
import * as types from '~/batch_comments/stores/modules/batch_comments/mutation_types';
+import mutations from '~/batch_comments/stores/modules/batch_comments/mutations';
+import createState from '~/batch_comments/stores/modules/batch_comments/state';
describe('Batch comments mutations', () => {
let state;
diff --git a/spec/frontend/behaviors/autosize_spec.js b/spec/frontend/behaviors/autosize_spec.js
index 352bd8a0ed0..a9dbee7fd08 100644
--- a/spec/frontend/behaviors/autosize_spec.js
+++ b/spec/frontend/behaviors/autosize_spec.js
@@ -1,12 +1,20 @@
import '~/behaviors/autosize';
-function load() {
- document.dispatchEvent(new Event('DOMContentLoaded'));
-}
-
jest.mock('~/helpers/startup_css_helper', () => {
return {
- waitForCSSLoaded: jest.fn().mockImplementation((cb) => cb.apply()),
+ waitForCSSLoaded: jest.fn().mockImplementation((cb) => {
+ // This is a hack:
+ // autosize.js will execute and modify the DOM
+ // whenever waitForCSSLoaded calls its callback function.
+ // This setTimeout is here because everything within setTimeout will be queued
+ // as async code until the current call stack is executed.
+ // If we would not do this, the mock for waitForCSSLoaded would call its callback
+ // before the fixture in the beforeEach is set and the Test would fail.
+ // more on this here: https://johnresig.com/blog/how-javascript-timers-work/
+ setTimeout(() => {
+ cb.apply();
+ }, 0);
+ }),
};
});
@@ -16,9 +24,15 @@ describe('Autosize behavior', () => {
});
it('is applied to the textarea', () => {
- load();
-
- const textarea = document.querySelector('textarea');
- expect(textarea.classList).toContain('js-autosize-initialized');
+ // This is the second part of the Hack:
+ // Because we are forcing the mock for WaitForCSSLoaded and the very end of our callstack
+ // to call its callback. This querySelector needs to go to the very end of our callstack
+ // as well, if we would not have this setTimeout Function here, the querySelector
+ // would run before the mockImplementation called its callBack Function
+ // the DOM Manipulation didn't happen yet and the test would fail.
+ setTimeout(() => {
+ const textarea = document.querySelector('textarea');
+ expect(textarea.classList).toContain('js-autosize-initialized');
+ }, 0);
});
});
diff --git a/spec/frontend/behaviors/copy_as_gfm_spec.js b/spec/frontend/behaviors/copy_as_gfm_spec.js
index 16ea4ba8624..acff990e84a 100644
--- a/spec/frontend/behaviors/copy_as_gfm_spec.js
+++ b/spec/frontend/behaviors/copy_as_gfm_spec.js
@@ -1,5 +1,5 @@
-import * as commonUtils from '~/lib/utils/common_utils';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
+import * as commonUtils from '~/lib/utils/common_utils';
describe('CopyAsGFM', () => {
describe('CopyAsGFM.pasteGFM', () => {
diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js
index 6e476d84501..286ed269421 100644
--- a/spec/frontend/behaviors/gl_emoji_spec.js
+++ b/spec/frontend/behaviors/gl_emoji_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
-import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
import installGlEmojiElement from '~/behaviors/gl_emoji';
+import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
import * as EmojiUnicodeSupport from '~/emoji/support';
+import axios from '~/lib/utils/axios_utils';
jest.mock('~/emoji/support');
diff --git a/spec/frontend/blob/balsamiq/balsamiq_viewer_spec.js b/spec/frontend/blob/balsamiq/balsamiq_viewer_spec.js
index 09c49617bc5..d7531d15b9a 100644
--- a/spec/frontend/blob/balsamiq/balsamiq_viewer_spec.js
+++ b/spec/frontend/blob/balsamiq/balsamiq_viewer_spec.js
@@ -1,7 +1,7 @@
import sqljs from 'sql.js';
import ClassSpecHelper from 'helpers/class_spec_helper';
-import axios from '~/lib/utils/axios_utils';
import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
+import axios from '~/lib/utils/axios_utils';
jest.mock('sql.js');
diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap
index b54efb93bc9..31fb6addcac 100644
--- a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap
+++ b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap
@@ -9,7 +9,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = `
/>
<div
- class="gl-display-none gl-display-sm-flex"
+ class="gl-display-none gl-sm-display-flex"
>
<viewer-switcher-stub
value="simple"
diff --git a/spec/frontend/blob/components/blob_content_error_spec.js b/spec/frontend/blob/components/blob_content_error_spec.js
index 95686be8294..0f5885c2acf 100644
--- a/spec/frontend/blob/components/blob_content_error_spec.js
+++ b/spec/frontend/blob/components/blob_content_error_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import BlobContentError from '~/blob/components/blob_content_error.vue';
import { BLOB_RENDER_ERRORS } from '~/blob/components/constants';
diff --git a/spec/frontend/blob/components/blob_content_spec.js b/spec/frontend/blob/components/blob_content_spec.js
index 3db95e5ad3f..8450c6b9332 100644
--- a/spec/frontend/blob/components/blob_content_spec.js
+++ b/spec/frontend/blob/components/blob_content_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import BlobContent from '~/blob/components/blob_content.vue';
import BlobContentError from '~/blob/components/blob_content_error.vue';
import {
@@ -7,6 +7,7 @@ import {
BLOB_RENDER_EVENT_SHOW_SOURCE,
BLOB_RENDER_ERRORS,
} from '~/blob/components/constants';
+import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
import {
Blob,
RichViewerMock,
@@ -14,7 +15,6 @@ import {
RichBlobContentMock,
SimpleBlobContentMock,
} from './mock_data';
-import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
describe('Blob Content component', () => {
let wrapper;
diff --git a/spec/frontend/blob/components/blob_edit_header_spec.js b/spec/frontend/blob/components/blob_edit_header_spec.js
index 22e8e6d986c..ac3080c65a5 100644
--- a/spec/frontend/blob/components/blob_edit_header_spec.js
+++ b/spec/frontend/blob/components/blob_edit_header_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormInput, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import BlobEditHeader from '~/blob/components/blob_edit_header.vue';
describe('Blob Header Editing', () => {
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 e2c73a5d5d9..bce65899c43 100644
--- a/spec/frontend/blob/components/blob_header_default_actions_spec.js
+++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlButtonGroup, GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import BlobHeaderActions from '~/blob/components/blob_header_default_actions.vue';
import {
BTN_COPY_CONTENTS_TITLE,
diff --git a/spec/frontend/blob/components/blob_header_filepath_spec.js b/spec/frontend/blob/components/blob_header_filepath_spec.js
index 7b8b5050486..d935f73c0d1 100644
--- a/spec/frontend/blob/components/blob_header_filepath_spec.js
+++ b/spec/frontend/blob/components/blob_header_filepath_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import BlobHeaderFilepath from '~/blob/components/blob_header_filepath.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { Blob as MockBlob } from './mock_data';
-import { numberToHumanSize } from '~/lib/utils/number_utils';
jest.mock('~/lib/utils/number_utils', () => ({
numberToHumanSize: jest.fn(() => 'a lot'),
diff --git a/spec/frontend/blob/components/blob_header_spec.js b/spec/frontend/blob/components/blob_header_spec.js
index b3f80183f6b..865e8ab1124 100644
--- a/spec/frontend/blob/components/blob_header_spec.js
+++ b/spec/frontend/blob/components/blob_header_spec.js
@@ -1,8 +1,8 @@
import { shallowMount, mount } from '@vue/test-utils';
import BlobHeader from '~/blob/components/blob_header.vue';
-import ViewerSwitcher from '~/blob/components/blob_header_viewer_switcher.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 { Blob } from './mock_data';
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 cf1101bc22c..9a560ec11f7 100644
--- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
+++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlButtonGroup, GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import BlobHeaderViewerSwitcher from '~/blob/components/blob_header_viewer_switcher.vue';
import {
RICH_BLOB_VIEWER,
diff --git a/spec/frontend/blob/notebook/notebook_viever_spec.js b/spec/frontend/blob/notebook/notebook_viever_spec.js
index f6a926a5ecb..604104bb31f 100644
--- a/spec/frontend/blob/notebook/notebook_viever_spec.js
+++ b/spec/frontend/blob/notebook/notebook_viever_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import component from '~/blob/notebook/notebook_viewer.vue';
+import axios from '~/lib/utils/axios_utils';
import NotebookLab from '~/notebook/index.vue';
describe('iPython notebook renderer', () => {
diff --git a/spec/frontend/blob/pdf/pdf_viewer_spec.js b/spec/frontend/blob/pdf/pdf_viewer_spec.js
index 0eea3aea639..e332ea49fa6 100644
--- a/spec/frontend/blob/pdf/pdf_viewer_spec.js
+++ b/spec/frontend/blob/pdf/pdf_viewer_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { FIXTURES_PATH } from 'spec/test_constants';
import component from '~/blob/pdf/pdf_viewer.vue';
diff --git a/spec/frontend/blob/pipeline_tour_success_modal_spec.js b/spec/frontend/blob/pipeline_tour_success_modal_spec.js
index d4562019302..f4af57de41f 100644
--- a/spec/frontend/blob/pipeline_tour_success_modal_spec.js
+++ b/spec/frontend/blob/pipeline_tour_success_modal_spec.js
@@ -1,8 +1,8 @@
+import { GlSprintf, GlModal, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
-import { GlSprintf, GlModal, GlLink } from '@gitlab/ui';
-import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import { stubComponent } from 'helpers/stub_component';
+import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import pipelineTourSuccess from '~/blob/pipeline_tour_success_modal.vue';
import modalProps from './pipeline_tour_success_mock_data';
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 e55b8e4af24..7e13994f2b7 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
@@ -1,6 +1,6 @@
+import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
-import { GlButton } from '@gitlab/ui';
import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import * as utils from '~/lib/utils/common_utils';
diff --git a/spec/frontend/blob/utils_spec.js b/spec/frontend/blob/utils_spec.js
index fa83690c104..3ff2e47e0b6 100644
--- a/spec/frontend/blob/utils_spec.js
+++ b/spec/frontend/blob/utils_spec.js
@@ -1,5 +1,5 @@
-import Editor from '~/editor/editor_lite';
import * as utils from '~/blob/utils';
+import Editor from '~/editor/editor_lite';
jest.mock('~/editor/editor_lite');
diff --git a/spec/frontend/blob/viewer/index_spec.js b/spec/frontend/blob/viewer/index_spec.js
index 4a5eb31602d..7449de48ec0 100644
--- a/spec/frontend/blob/viewer/index_spec.js
+++ b/spec/frontend/blob/viewer/index_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable no-new */
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import { setTestTimeout } from 'helpers/timeout';
import BlobViewer from '~/blob/viewer/index';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js
index 7927c14d2ac..3134feedcf3 100644
--- a/spec/frontend/blob_edit/edit_blob_spec.js
+++ b/spec/frontend/blob_edit/edit_blob_spec.js
@@ -1,8 +1,8 @@
import waitForPromises from 'helpers/wait_for_promises';
import EditBlob from '~/blob_edit/edit_blob';
import EditorLite from '~/editor/editor_lite';
-import { EditorMarkdownExtension } from '~/editor/extensions/editor_markdown_ext';
import { FileTemplateExtension } from '~/editor/extensions/editor_file_template_ext';
+import { EditorMarkdownExtension } from '~/editor/extensions/editor_markdown_ext';
jest.mock('~/editor/editor_lite');
jest.mock('~/editor/extensions/editor_markdown_ext');
diff --git a/spec/frontend/boards/board_list_deprecated_spec.js b/spec/frontend/boards/board_list_deprecated_spec.js
index 393d7f954b1..b71564f7858 100644
--- a/spec/frontend/boards/board_list_deprecated_spec.js
+++ b/spec/frontend/boards/board_list_deprecated_spec.js
@@ -1,17 +1,16 @@
/* global List */
/* global ListIssue */
-
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
-import eventHub from '~/boards/eventhub';
import BoardList from '~/boards/components/board_list_deprecated.vue';
+import eventHub from '~/boards/eventhub';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
import '~/boards/models/issue';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor } from './mock_data';
-import store from '~/boards/stores';
-import boardsStore from '~/boards/stores/boards_store';
const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listProps = {} }) => {
const el = document.createElement('div');
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index f82b1f7ed5c..915b470df8d 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -1,17 +1,15 @@
/* global List */
/* global ListIssue */
-
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
import Sortable from 'sortablejs';
-import axios from '~/lib/utils/axios_utils';
+import Vue from 'vue';
import BoardList from '~/boards/components/board_list_deprecated.vue';
-
import '~/boards/models/issue';
import '~/boards/models/list';
-import { listObj, boardsMockInterceptor } from './mock_data';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
+import { listObj, boardsMockInterceptor } from './mock_data';
window.Sortable = Sortable;
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 1b62f25044e..7ed20f20882 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -1,11 +1,11 @@
+import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
-import { createLocalVue, mount } from '@vue/test-utils';
-import eventHub from '~/boards/eventhub';
-import BoardList from '~/boards/components/board_list.vue';
import BoardCard from '~/boards/components/board_card.vue';
-import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data';
+import BoardList from '~/boards/components/board_list.vue';
+import eventHub from '~/boards/eventhub';
import defaultState from '~/boards/stores/state';
+import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/boards/board_new_issue_deprecated_spec.js b/spec/frontend/boards/board_new_issue_deprecated_spec.js
index 8236b468189..1a29f680166 100644
--- a/spec/frontend/boards/board_new_issue_deprecated_spec.js
+++ b/spec/frontend/boards/board_new_issue_deprecated_spec.js
@@ -1,11 +1,11 @@
/* global List */
-import Vue from 'vue';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
+import Vue from 'vue';
import boardNewIssue from '~/boards/components/board_new_issue_deprecated.vue';
import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor } from './mock_data';
diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js
index f1d249ff069..02881333273 100644
--- a/spec/frontend/boards/boards_store_spec.js
+++ b/spec/frontend/boards/boards_store_spec.js
@@ -1,12 +1,12 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
-import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/boards/eventhub';
-import { listObj, listObjDuplicate } from './mock_data';
import ListIssue from '~/boards/models/issue';
import List from '~/boards/models/list';
+import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
+import { listObj, listObjDuplicate } from './mock_data';
jest.mock('js-cookie');
diff --git a/spec/frontend/boards/boards_util_spec.js b/spec/frontend/boards/boards_util_spec.js
new file mode 100644
index 00000000000..0feb1411003
--- /dev/null
+++ b/spec/frontend/boards/boards_util_spec.js
@@ -0,0 +1,17 @@
+import { transformNotFilters } from '~/boards/boards_util';
+
+describe('transformNotFilters', () => {
+ const filters = {
+ 'not[labelName]': ['label'],
+ 'not[assigneeUsername]': 'assignee',
+ };
+
+ it('formats not filters, transforms epicId to fullEpicId', () => {
+ const result = transformNotFilters(filters);
+
+ expect(result).toEqual({
+ labelName: ['label'],
+ assigneeUsername: 'assignee',
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_assignee_dropdown_spec.js b/spec/frontend/boards/components/board_assignee_dropdown_spec.js
deleted file mode 100644
index e52c14f9783..00000000000
--- a/spec/frontend/boards/components/board_assignee_dropdown_spec.js
+++ /dev/null
@@ -1,380 +0,0 @@
-import { mount, createLocalVue } from '@vue/test-utils';
-import {
- GlDropdownItem,
- GlAvatarLink,
- GlAvatarLabeled,
- GlSearchBoxByType,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import VueApollo from 'vue-apollo';
-import BoardAssigneeDropdown from '~/boards/components/board_assignee_dropdown.vue';
-import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
-import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
-import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import store from '~/boards/stores';
-import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
-import searchUsers from '~/boards/graphql/users_search.query.graphql';
-import { participants } from '../mock_data';
-
-const localVue = createLocalVue();
-
-localVue.use(VueApollo);
-
-describe('BoardCardAssigneeDropdown', () => {
- let wrapper;
- let fakeApollo;
- let getIssueParticipantsSpy;
- let getSearchUsersSpy;
- let dispatchSpy;
-
- const iid = '111';
- const activeIssueName = 'test';
- const anotherIssueName = 'hello';
-
- const createComponent = (search = '', loading = false) => {
- wrapper = mount(BoardAssigneeDropdown, {
- data() {
- return {
- search,
- selected: [],
- participants,
- };
- },
- store,
- provide: {
- canUpdate: true,
- rootPath: '',
- },
- mocks: {
- $apollo: {
- queries: {
- participants: {
- loading,
- },
- },
- },
- },
- });
- };
-
- const createComponentWithApollo = (search = '') => {
- fakeApollo = createMockApollo([
- [getIssueParticipants, getIssueParticipantsSpy],
- [searchUsers, getSearchUsersSpy],
- ]);
- wrapper = mount(BoardAssigneeDropdown, {
- localVue,
- apolloProvider: fakeApollo,
- data() {
- return {
- search,
- selected: [],
- participants,
- };
- },
- store,
- provide: {
- canUpdate: true,
- rootPath: '',
- },
- });
- };
-
- const unassign = async () => {
- wrapper.find('[data-testid="unassign"]').trigger('click');
-
- await wrapper.vm.$nextTick();
- };
-
- const openDropdown = async () => {
- wrapper.find('[data-testid="edit-button"]').trigger('click');
-
- await wrapper.vm.$nextTick();
- };
-
- const findByText = (text) => {
- return wrapper.findAll(GlDropdownItem).wrappers.find((node) => node.text().indexOf(text) === 0);
- };
-
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- beforeEach(() => {
- store.state.activeId = '1';
- store.state.issues = {
- 1: {
- iid,
- assignees: [{ username: activeIssueName, name: activeIssueName, id: activeIssueName }],
- },
- };
-
- dispatchSpy = jest.spyOn(store, 'dispatch').mockResolvedValue();
- });
-
- afterEach(() => {
- window.gon = {};
- jest.restoreAllMocks();
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when mounted', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it.each`
- text
- ${anotherIssueName}
- ${activeIssueName}
- `('finds item with $text', ({ text }) => {
- const item = findByText(text);
-
- expect(item.exists()).toBe(true);
- });
-
- it('renders gl-avatar-link in gl-dropdown-item', () => {
- const item = findByText('hello');
-
- expect(item.find(GlAvatarLink).exists()).toBe(true);
- });
-
- it('renders gl-avatar-labeled in gl-avatar-link', () => {
- const item = findByText('hello');
-
- expect(item.find(GlAvatarLink).find(GlAvatarLabeled).exists()).toBe(true);
- });
- });
-
- describe('when selected users are present', () => {
- it('renders a divider', () => {
- createComponent();
-
- expect(wrapper.find('[data-testid="selected-user-divider"]').exists()).toBe(true);
- });
- });
-
- describe('when collapsed', () => {
- it('renders IssuableAssignees', () => {
- createComponent();
-
- expect(wrapper.find(IssuableAssignees).isVisible()).toBe(true);
- expect(wrapper.find(MultiSelectDropdown).isVisible()).toBe(false);
- });
- });
-
- describe('when dropdown is open', () => {
- beforeEach(async () => {
- createComponent();
-
- await openDropdown();
- });
-
- it('shows assignees dropdown', async () => {
- expect(wrapper.find(IssuableAssignees).isVisible()).toBe(false);
- expect(wrapper.find(MultiSelectDropdown).isVisible()).toBe(true);
- });
-
- it('shows the issue returned as the activeIssue', async () => {
- expect(findByText(activeIssueName).props('isChecked')).toBe(true);
- });
-
- describe('when "Unassign" is clicked', () => {
- it('unassigns assignees', async () => {
- await unassign();
-
- expect(findByText('Unassign').props('isChecked')).toBe(true);
- });
- });
-
- describe('when an unselected item is clicked', () => {
- beforeEach(async () => {
- await unassign();
- });
-
- it('assigns assignee in the dropdown', async () => {
- wrapper.find('[data-testid="item_test"]').trigger('click');
-
- await wrapper.vm.$nextTick();
-
- expect(findByText(activeIssueName).props('isChecked')).toBe(true);
- });
-
- it('calls setAssignees with username list', async () => {
- wrapper.find('[data-testid="item_test"]').trigger('click');
-
- await wrapper.vm.$nextTick();
-
- document.body.click();
-
- await wrapper.vm.$nextTick();
-
- expect(store.dispatch).toHaveBeenCalledWith('setAssignees', [activeIssueName]);
- });
- });
-
- describe('when the user off clicks', () => {
- beforeEach(async () => {
- await unassign();
-
- document.body.click();
-
- await wrapper.vm.$nextTick();
- });
-
- it('calls setAssignees with username list', async () => {
- expect(store.dispatch).toHaveBeenCalledWith('setAssignees', []);
- });
-
- it('closes the dropdown', async () => {
- expect(wrapper.find(IssuableAssignees).isVisible()).toBe(true);
- });
- });
- });
-
- it('renders divider after unassign', () => {
- createComponent();
-
- expect(wrapper.find('[data-testid="unassign-divider"]').exists()).toBe(true);
- });
-
- it.each`
- assignees | expected
- ${[{ id: 5, username: '', name: '' }]} | ${'Assignee'}
- ${[{ id: 6, username: '', name: '' }, { id: 7, username: '', name: '' }]} | ${'2 Assignees'}
- `(
- 'when assignees have a length of $assignees.length, it renders $expected',
- ({ assignees, expected }) => {
- store.state.issues['1'].assignees = assignees;
-
- createComponent();
-
- expect(wrapper.find(BoardEditableItem).props('title')).toBe(expected);
- },
- );
-
- describe('when participants is loading', () => {
- beforeEach(() => {
- createComponent('', true);
- });
-
- it('finds a loading icon in the dropdown', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- });
- });
-
- describe('when participants is loading is false', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('does not find GlLoading icon in the dropdown', () => {
- expect(findLoadingIcon().exists()).toBe(false);
- });
-
- it('finds at least 1 GlDropdownItem', () => {
- expect(wrapper.findAll(GlDropdownItem).length).toBeGreaterThan(0);
- });
- });
-
- describe('Apollo', () => {
- beforeEach(() => {
- getIssueParticipantsSpy = jest.fn().mockResolvedValue({
- data: {
- issue: {
- participants: {
- nodes: [
- {
- username: 'participant',
- name: 'participant',
- webUrl: '',
- avatarUrl: '',
- id: '',
- },
- ],
- },
- },
- },
- });
- getSearchUsersSpy = jest.fn().mockResolvedValue({
- data: {
- users: {
- nodes: [{ username: 'root', name: 'root', webUrl: '', avatarUrl: '', id: '' }],
- },
- },
- });
- });
-
- describe('when search is empty', () => {
- beforeEach(() => {
- createComponentWithApollo();
- });
-
- it('calls getIssueParticipants', async () => {
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
-
- expect(getIssueParticipantsSpy).toHaveBeenCalledWith({ id: 'gid://gitlab/Issue/111' });
- });
- });
-
- describe('when search is not empty', () => {
- beforeEach(() => {
- createComponentWithApollo('search term');
- });
-
- it('calls searchUsers', async () => {
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
-
- expect(getSearchUsersSpy).toHaveBeenCalledWith({ search: 'search term' });
- });
- });
- });
-
- it('finds GlSearchBoxByType', async () => {
- createComponent();
-
- await openDropdown();
-
- expect(wrapper.find(GlSearchBoxByType).exists()).toBe(true);
- });
-
- describe('when assign-self is emitted from IssuableAssignees', () => {
- const currentUser = { username: 'self', name: '', id: '' };
-
- beforeEach(() => {
- window.gon = { current_username: currentUser.username };
-
- dispatchSpy.mockResolvedValue([currentUser]);
- createComponent();
-
- wrapper.find(IssuableAssignees).vm.$emit('assign-self');
- });
-
- it('calls setAssignees with currentUser', () => {
- expect(store.dispatch).toHaveBeenCalledWith('setAssignees', currentUser.username);
- });
-
- it('adds the user to the selected list', async () => {
- expect(findByText(currentUser.username).exists()).toBe(true);
- });
- });
-
- describe('when setting an assignee', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('passes loading state from Vuex to BoardEditableItem', async () => {
- store.state.isSettingAssignees = true;
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.find(BoardEditableItem).props('loading')).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/boards/components/board_card_layout_deprecated_spec.js b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js
new file mode 100644
index 00000000000..426c5289ba6
--- /dev/null
+++ b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js
@@ -0,0 +1,158 @@
+/* global List */
+/* global ListLabel */
+
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+
+import MockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import '~/boards/models/label';
+import '~/boards/models/assignee';
+import '~/boards/models/list';
+import BoardCardLayout from '~/boards/components/board_card_layout_deprecated.vue';
+import issueCardInner from '~/boards/components/issue_card_inner.vue';
+import { ISSUABLE } from '~/boards/constants';
+import boardsVuexStore from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
+import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
+
+describe('Board card layout', () => {
+ let wrapper;
+ let mock;
+ let list;
+ let store;
+
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const createStore = ({ getters = {}, actions = {} } = {}) => {
+ store = new Vuex.Store({
+ ...boardsVuexStore,
+ actions,
+ getters,
+ });
+ };
+
+ // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized
+ const mountComponent = ({ propsData = {}, provide = {} } = {}) => {
+ wrapper = shallowMount(BoardCardLayout, {
+ localVue,
+ stubs: {
+ issueCardInner,
+ },
+ store,
+ propsData: {
+ list,
+ issue: list.issues[0],
+ disabled: false,
+ index: 0,
+ ...propsData,
+ },
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ scopedLabelsAvailable: false,
+ ...provide,
+ },
+ });
+ };
+
+ const setupData = () => {
+ list = new List(listObj);
+ boardsStore.create();
+ boardsStore.detail.issue = {};
+ const label1 = new ListLabel({
+ id: 3,
+ title: 'testing 123',
+ color: '#000cff',
+ text_color: 'white',
+ description: 'test',
+ });
+ return waitForPromises().then(() => {
+ list.issues[0].labels.push(label1);
+ });
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onAny().reply(boardsMockInterceptor);
+ setMockEndpoints();
+ return setupData();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ list = null;
+ mock.restore();
+ });
+
+ describe('mouse events', () => {
+ it('sets showDetail to true on mousedown', async () => {
+ createStore();
+ mountComponent();
+
+ wrapper.trigger('mousedown');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.showDetail).toBe(true);
+ });
+
+ it('sets showDetail to false on mousemove', async () => {
+ createStore();
+ mountComponent();
+ wrapper.trigger('mousedown');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.showDetail).toBe(true);
+ wrapper.trigger('mousemove');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.showDetail).toBe(false);
+ });
+
+ it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => {
+ const setActiveId = jest.fn();
+ createStore({
+ actions: {
+ setActiveId,
+ },
+ });
+ mountComponent({
+ provide: {
+ glFeatures: { graphqlBoardLists: true },
+ },
+ });
+
+ wrapper.trigger('mouseup');
+ await wrapper.vm.$nextTick();
+
+ expect(setActiveId).toHaveBeenCalledTimes(1);
+ expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
+ id: list.issues[0].id,
+ sidebarType: ISSUABLE,
+ });
+ });
+
+ it("calls 'setActiveId' when epic swimlanes is active", async () => {
+ const setActiveId = jest.fn();
+ const isSwimlanesOn = () => true;
+ createStore({
+ getters: { isSwimlanesOn },
+ actions: {
+ setActiveId,
+ },
+ });
+ mountComponent();
+
+ wrapper.trigger('mouseup');
+ await wrapper.vm.$nextTick();
+
+ expect(setActiveId).toHaveBeenCalledTimes(1);
+ expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
+ id: list.issues[0].id,
+ sidebarType: ISSUABLE,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_card_layout_spec.js b/spec/frontend/boards/components/board_card_layout_spec.js
index d8633871e8d..3fa8714807c 100644
--- a/spec/frontend/boards/components/board_card_layout_spec.js
+++ b/spec/frontend/boards/components/board_card_layout_spec.js
@@ -1,28 +1,14 @@
-/* global List */
-/* global ListLabel */
-
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
-import MockAdapter from 'axios-mock-adapter';
-import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
-
-import '~/boards/models/label';
-import '~/boards/models/assignee';
-import '~/boards/models/list';
-import boardsVuexStore from '~/boards/stores';
-import boardsStore from '~/boards/stores/boards_store';
import BoardCardLayout from '~/boards/components/board_card_layout.vue';
-import issueCardInner from '~/boards/components/issue_card_inner.vue';
-import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
-
+import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import { ISSUABLE } from '~/boards/constants';
+import defaultState from '~/boards/stores/state';
+import { mockLabelList, mockIssue } from '../mock_data';
describe('Board card layout', () => {
let wrapper;
- let mock;
- let list;
let store;
const localVue = createLocalVue();
@@ -30,7 +16,7 @@ describe('Board card layout', () => {
const createStore = ({ getters = {}, actions = {} } = {}) => {
store = new Vuex.Store({
- ...boardsVuexStore,
+ state: defaultState,
actions,
getters,
});
@@ -41,12 +27,12 @@ describe('Board card layout', () => {
wrapper = shallowMount(BoardCardLayout, {
localVue,
stubs: {
- issueCardInner,
+ IssueCardInner,
},
store,
propsData: {
- list,
- issue: list.issues[0],
+ list: mockLabelList,
+ issue: mockIssue,
disabled: false,
index: 0,
...propsData,
@@ -60,34 +46,9 @@ describe('Board card layout', () => {
});
};
- const setupData = () => {
- list = new List(listObj);
- boardsStore.create();
- boardsStore.detail.issue = {};
- const label1 = new ListLabel({
- id: 3,
- title: 'testing 123',
- color: '#000cff',
- text_color: 'white',
- description: 'test',
- });
- return waitForPromises().then(() => {
- list.issues[0].labels.push(label1);
- });
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onAny().reply(boardsMockInterceptor);
- setMockEndpoints();
- return setupData();
- });
-
afterEach(() => {
wrapper.destroy();
wrapper = null;
- list = null;
- mock.restore();
});
describe('mouse events', () => {
@@ -112,25 +73,21 @@ describe('Board card layout', () => {
expect(wrapper.vm.showDetail).toBe(false);
});
- it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => {
+ it("calls 'setActiveId'", async () => {
const setActiveId = jest.fn();
createStore({
actions: {
setActiveId,
},
});
- mountComponent({
- provide: {
- glFeatures: { graphqlBoardLists: true },
- },
- });
+ mountComponent();
wrapper.trigger('mouseup');
await wrapper.vm.$nextTick();
expect(setActiveId).toHaveBeenCalledTimes(1);
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
- id: list.issues[0].id,
+ id: mockIssue.id,
sidebarType: ISSUABLE,
});
});
@@ -151,7 +108,7 @@ describe('Board card layout', () => {
expect(setActiveId).toHaveBeenCalledTimes(1);
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
- id: list.issues[0].id,
+ id: mockIssue.id,
sidebarType: ISSUABLE,
});
});
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index 1084009caad..5f26ae1bb3b 100644
--- a/spec/frontend/boards/components/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -6,17 +6,17 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
+import BoardCard from '~/boards/components/board_card.vue';
+import issueCardInner from '~/boards/components/issue_card_inner.vue';
+import eventHub from '~/boards/eventhub';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
import axios from '~/lib/utils/axios_utils';
-import eventHub from '~/boards/eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/list';
-import store from '~/boards/stores';
-import boardsStore from '~/boards/stores/boards_store';
-import BoardCard from '~/boards/components/board_card.vue';
-import issueCardInner from '~/boards/components/issue_card_inner.vue';
import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
diff --git a/spec/frontend/boards/components/board_column_deprecated_spec.js b/spec/frontend/boards/components/board_column_deprecated_spec.js
index a703caca4eb..e6d65e48c3f 100644
--- a/spec/frontend/boards/components/board_column_deprecated_spec.js
+++ b/spec/frontend/boards/components/board_column_deprecated_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vue, { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { listObj } from 'jest/boards/mock_data';
import Board from '~/boards/components/board_column_deprecated.vue';
-import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
+import List from '~/boards/models/list';
import axios from '~/lib/utils/axios_utils';
describe('Board Column Component', () => {
@@ -30,6 +30,7 @@ describe('Board Column Component', () => {
const createComponent = ({
listType = ListType.backlog,
collapsed = false,
+ highlighted = false,
withLocalStorage = true,
} = {}) => {
const boardId = '1';
@@ -37,6 +38,7 @@ describe('Board Column Component', () => {
const listMock = {
...listObj,
list_type: listType,
+ highlighted,
collapsed,
};
@@ -91,4 +93,14 @@ describe('Board Column Component', () => {
expect(isCollapsed()).toBe(true);
});
});
+
+ describe('highlighting', () => {
+ it('scrolls to column when highlighted', async () => {
+ createComponent({ highlighted: true });
+
+ await nextTick();
+
+ expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js
index 1dcdad2b492..4e523d636cd 100644
--- a/spec/frontend/boards/components/board_column_spec.js
+++ b/spec/frontend/boards/components/board_column_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { listObj } from 'jest/boards/mock_data';
import BoardColumn from '~/boards/components/board_column.vue';
@@ -66,4 +67,16 @@ describe('Board Column Component', () => {
expect(isCollapsed()).toBe(true);
});
});
+
+ describe('highlighting', () => {
+ it('scrolls to column when highlighted', async () => {
+ createComponent();
+
+ store.state.highlightedLists.push(listObj.id);
+
+ await nextTick();
+
+ expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_configuration_options_spec.js b/spec/frontend/boards/components/board_configuration_options_spec.js
index d9614c254e2..6f0971a9458 100644
--- a/spec/frontend/boards/components/board_configuration_options_spec.js
+++ b/spec/frontend/boards/components/board_configuration_options_spec.js
@@ -7,6 +7,7 @@ describe('BoardConfigurationOptions', () => {
const defaultProps = {
hideBacklogList: false,
hideClosedList: false,
+ readonly: false,
};
const createComponent = (props = {}) => {
@@ -61,4 +62,18 @@ describe('BoardConfigurationOptions', () => {
expect(wrapper.emitted('update:hideClosedList')).toEqual([[true]]);
});
+
+ it('renders checkboxes disabled when user does not have edit rights', () => {
+ createComponent({ readonly: true });
+
+ expect(closedListCheckbox().attributes('disabled')).toBe('true');
+ expect(backlogListCheckbox().attributes('disabled')).toBe('true');
+ });
+
+ it('renders checkboxes enabled when user has edit rights', () => {
+ createComponent();
+
+ expect(closedListCheckbox().attributes('disabled')).toBeUndefined();
+ expect(backlogListCheckbox().attributes('disabled')).toBeUndefined();
+ });
});
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index 98be02d7dbf..159b67ccc67 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -1,12 +1,12 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import Draggable from 'vuedraggable';
+import Vuex from 'vuex';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import getters from 'ee_else_ce/boards/stores/getters';
import BoardColumnDeprecated from '~/boards/components/board_column_deprecated.vue';
-import { mockLists, mockListsWithModel } from '../mock_data';
import BoardContent from '~/boards/components/board_content.vue';
+import { mockLists, mockListsWithModel } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index c34987a55de..858efea99ad 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,16 +1,15 @@
+import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-
import { TEST_HOST } from 'helpers/test_constants';
-import { GlModal } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { visitUrl } from '~/lib/utils/url_utility';
-import boardsStore from '~/boards/stores/boards_store';
import BoardForm from '~/boards/components/board_form.vue';
-import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
+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 { deprecatedCreateFlash as createFlash } from '~/flash';
+import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrlMock'),
@@ -35,6 +34,7 @@ const defaultProps = {
labelsPath: `${TEST_HOST}/labels/path`,
labelsWebUrl: `${TEST_HOST}/-/labels`,
currentBoard,
+ currentPage: '',
};
describe('BoardForm', () => {
@@ -75,14 +75,12 @@ describe('BoardForm', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- boardsStore.state.currentPage = null;
mutate = null;
});
describe('when user can not admin the board', () => {
beforeEach(() => {
- boardsStore.state.currentPage = 'new';
- createComponent();
+ createComponent({ currentPage: formType.new });
});
it('hides modal footer when user is not a board admin', () => {
@@ -100,8 +98,7 @@ describe('BoardForm', () => {
describe('when user can admin the board', () => {
beforeEach(() => {
- boardsStore.state.currentPage = 'new';
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
});
it('shows modal footer when user is a board admin', () => {
@@ -118,13 +115,9 @@ describe('BoardForm', () => {
});
describe('when creating a new board', () => {
- beforeEach(() => {
- boardsStore.state.currentPage = 'new';
- });
-
describe('on non-scoped-board', () => {
beforeEach(() => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
});
it('clears the form', () => {
@@ -165,7 +158,7 @@ describe('BoardForm', () => {
});
it('does not call API if board name is empty', async () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
findInput().trigger('keyup.enter', { metaKey: true });
await waitForPromises();
@@ -174,7 +167,7 @@ describe('BoardForm', () => {
});
it('calls a correct GraphQL mutation and redirects to correct page from existing board', async () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
fillForm();
await waitForPromises();
@@ -194,7 +187,7 @@ describe('BoardForm', () => {
it('shows an error flash if GraphQL mutation fails', async () => {
mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
fillForm();
await waitForPromises();
@@ -209,13 +202,9 @@ describe('BoardForm', () => {
});
describe('when editing a board', () => {
- beforeEach(() => {
- boardsStore.state.currentPage = 'edit';
- });
-
describe('on non-scoped-board', () => {
beforeEach(() => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
});
it('clears the form', () => {
@@ -247,7 +236,7 @@ describe('BoardForm', () => {
},
});
window.location = new URL('https://test/boards/1');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
@@ -273,7 +262,7 @@ describe('BoardForm', () => {
},
});
window.location = new URL('https://test/boards/1?group_by=epic');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
@@ -294,7 +283,7 @@ describe('BoardForm', () => {
it('shows an error flash if GraphQL mutation fails', async () => {
mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
await waitForPromises();
@@ -308,24 +297,20 @@ describe('BoardForm', () => {
});
describe('when deleting a board', () => {
- beforeEach(() => {
- boardsStore.state.currentPage = 'delete';
- });
-
it('passes correct primary action text and variant', () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
expect(findModalActionPrimary().text).toBe('Delete');
expect(findModalActionPrimary().attributes[0].variant).toBe('danger');
});
it('renders delete confirmation message', () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
expect(findDeleteConfirmation().exists()).toBe(true);
});
it('calls a correct GraphQL mutation and redirects to correct page after deleting board', async () => {
mutate = jest.fn().mockResolvedValue({});
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
findModal().vm.$emit('primary');
await waitForPromises();
@@ -343,7 +328,7 @@ describe('BoardForm', () => {
it('shows an error flash if GraphQL mutation fails', async () => {
mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
findModal().vm.$emit('primary');
await waitForPromises();
diff --git a/spec/frontend/boards/components/board_list_header_deprecated_spec.js b/spec/frontend/boards/components/board_list_header_deprecated_spec.js
index 6207724e6a9..fdc7cd2b1d4 100644
--- a/spec/frontend/boards/components/board_list_header_deprecated_spec.js
+++ b/spec/frontend/boards/components/board_list_header_deprecated_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { listObj } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header_deprecated.vue';
-import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
+import List from '~/boards/models/list';
import axios from '~/lib/utils/axios_utils';
describe('Board List Header Component', () => {
@@ -74,7 +74,13 @@ describe('Board List Header Component', () => {
describe('Add issue button', () => {
const hasNoAddButton = [ListType.closed];
- const hasAddButton = [ListType.backlog, ListType.label, ListType.milestone, ListType.assignee];
+ const hasAddButton = [
+ ListType.backlog,
+ ListType.label,
+ ListType.milestone,
+ ListType.iteration,
+ ListType.assignee,
+ ];
it.each(hasNoAddButton)('does not render when List Type is `%s`', (listType) => {
createComponent({ listType });
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 357d05ced02..f30e3792435 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { mockLabelList } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header.vue';
@@ -78,7 +78,13 @@ describe('Board List Header Component', () => {
describe('Add issue button', () => {
const hasNoAddButton = [ListType.closed];
- const hasAddButton = [ListType.backlog, ListType.label, ListType.milestone, ListType.assignee];
+ const hasAddButton = [
+ ListType.backlog,
+ ListType.label,
+ ListType.milestone,
+ ListType.iteration,
+ ListType.assignee,
+ ];
it.each(hasNoAddButton)('does not render when List Type is `%s`', (listType) => {
createComponent({ listType });
@@ -167,7 +173,7 @@ describe('Board List Header Component', () => {
describe('user can drag', () => {
const cannotDragList = [ListType.backlog, ListType.closed];
- const canDragList = [ListType.label, ListType.milestone, ListType.assignee];
+ const canDragList = [ListType.label, ListType.milestone, ListType.iteration, ListType.assignee];
it.each(cannotDragList)(
'does not have user-can-drag-class so user cannot drag list',
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index 5a01221a5be..ce8c95527e9 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
import '~/boards/models/list';
diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js
index 12c9431f2d4..52b4d71f7b9 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -1,14 +1,14 @@
import '~/boards/models/list';
-import MockAdapter from 'axios-mock-adapter';
+import { GlDrawer, GlLabel } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { GlDrawer, GlLabel } from '@gitlab/ui';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
-import boardsStore from '~/boards/stores/boards_store';
+import { inactiveId, LIST } from '~/boards/constants';
import { createStore } from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
import sidebarEventHub from '~/sidebar/event_hub';
-import { inactiveId, LIST } from '~/boards/constants';
const localVue = createLocalVue();
diff --git a/spec/frontend/boards/components/boards_selector_deprecated_spec.js b/spec/frontend/boards/components/boards_selector_deprecated_spec.js
new file mode 100644
index 00000000000..cc078861d75
--- /dev/null
+++ b/spec/frontend/boards/components/boards_selector_deprecated_spec.js
@@ -0,0 +1,214 @@
+import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { TEST_HOST } from 'spec/test_constants';
+import BoardsSelector from '~/boards/components/boards_selector_deprecated.vue';
+import boardsStore from '~/boards/stores/boards_store';
+
+const throttleDuration = 1;
+
+function boardGenerator(n) {
+ return new Array(n).fill().map((board, index) => {
+ const id = `${index}`;
+ const name = `board${id}`;
+
+ return {
+ id,
+ name,
+ };
+ });
+}
+
+describe('BoardsSelector', () => {
+ let wrapper;
+ let allBoardsResponse;
+ let recentBoardsResponse;
+ const boards = boardGenerator(20);
+ const recentBoards = boardGenerator(5);
+
+ const fillSearchBox = (filterTerm) => {
+ const searchBox = wrapper.find({ ref: 'searchBox' });
+ const searchBoxInput = searchBox.find('input');
+ searchBoxInput.setValue(filterTerm);
+ searchBoxInput.trigger('input');
+ };
+
+ const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
+ const getDropdownHeaders = () => wrapper.findAll(GlDropdownSectionHeader);
+ const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findDropdown = () => wrapper.find(GlDropdown);
+
+ beforeEach(() => {
+ const $apollo = {
+ queries: {
+ boards: {
+ loading: false,
+ },
+ },
+ };
+
+ boardsStore.setEndpoints({
+ boardsEndpoint: '',
+ recentBoardsEndpoint: '',
+ listsEndpoint: '',
+ bulkUpdatePath: '',
+ boardId: '',
+ });
+
+ allBoardsResponse = Promise.resolve({
+ data: {
+ group: {
+ boards: {
+ edges: boards.map((board) => ({ node: board })),
+ },
+ },
+ },
+ });
+ recentBoardsResponse = Promise.resolve({
+ data: recentBoards,
+ });
+
+ boardsStore.allBoards = jest.fn(() => allBoardsResponse);
+ boardsStore.recentBoards = jest.fn(() => recentBoardsResponse);
+
+ wrapper = mount(BoardsSelector, {
+ propsData: {
+ throttleDuration,
+ currentBoard: {
+ id: 1,
+ name: 'Development',
+ milestone_id: null,
+ weight: null,
+ assignee_id: null,
+ labels: [],
+ },
+ boardBaseUrl: `${TEST_HOST}/board/base/url`,
+ hasMissingBoards: false,
+ canAdminBoard: true,
+ multipleIssueBoardsAvailable: true,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ labelsWebUrl: `${TEST_HOST}/labels`,
+ projectId: 42,
+ groupId: 19,
+ scopedIssueBoardFeatureEnabled: true,
+ weights: [],
+ },
+ mocks: { $apollo },
+ attachTo: document.body,
+ });
+
+ wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
+ wrapper.setData({
+ [options.loadingKey]: true,
+ });
+ });
+
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ findDropdown().vm.$emit('show');
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('loading', () => {
+ // we are testing loading state, so don't resolve responses until after the tests
+ afterEach(() => {
+ return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
+ });
+
+ it('shows loading spinner', () => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ expect(getDropdownItems()).toHaveLength(0);
+ expect(getLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('loaded', () => {
+ beforeEach(async () => {
+ await wrapper.setData({
+ loadingBoards: false,
+ });
+ return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
+ });
+
+ it('hides loading spinner', () => {
+ expect(getLoadingIcon().exists()).toBe(false);
+ });
+
+ describe('filtering', () => {
+ beforeEach(() => {
+ wrapper.setData({
+ boards,
+ });
+
+ return nextTick();
+ });
+
+ it('shows all boards without filtering', () => {
+ expect(getDropdownItems()).toHaveLength(boards.length + recentBoards.length);
+ });
+
+ it('shows only matching boards when filtering', () => {
+ const filterTerm = 'board1';
+ const expectedCount = boards.filter((board) => board.name.includes(filterTerm)).length;
+
+ fillSearchBox(filterTerm);
+
+ return nextTick().then(() => {
+ expect(getDropdownItems()).toHaveLength(expectedCount);
+ });
+ });
+
+ it('shows message if there are no matching boards', () => {
+ fillSearchBox('does not exist');
+
+ return nextTick().then(() => {
+ expect(getDropdownItems()).toHaveLength(0);
+ expect(wrapper.text().includes('No matching boards found')).toBe(true);
+ });
+ });
+ });
+
+ describe('recent boards section', () => {
+ it('shows only when boards are greater than 10', () => {
+ wrapper.setData({
+ boards,
+ });
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(2);
+ });
+ });
+
+ it('does not show when boards are less than 10', () => {
+ wrapper.setData({
+ boards: boards.slice(0, 5),
+ });
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ });
+ });
+
+ it('does not show when recentBoards api returns empty array', () => {
+ wrapper.setData({
+ recentBoards: [],
+ });
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ });
+ });
+
+ it('does not show when search is active', () => {
+ fillSearchBox('Random string');
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 81575bf486a..bf317b51e83 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -1,9 +1,10 @@
-import { nextTick } from 'vue';
-import { mount } from '@vue/test-utils';
import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import BoardsSelector from '~/boards/components/boards_selector.vue';
-import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
const throttleDuration = 1;
@@ -23,6 +24,7 @@ describe('BoardsSelector', () => {
let wrapper;
let allBoardsResponse;
let recentBoardsResponse;
+ let mock;
const boards = boardGenerator(20);
const recentBoards = boardGenerator(5);
@@ -39,6 +41,7 @@ describe('BoardsSelector', () => {
const findDropdown = () => wrapper.find(GlDropdown);
beforeEach(() => {
+ mock = new MockAdapter(axios);
const $apollo = {
queries: {
boards: {
@@ -47,14 +50,6 @@ describe('BoardsSelector', () => {
},
};
- boardsStore.setEndpoints({
- boardsEndpoint: '',
- recentBoardsEndpoint: '',
- listsEndpoint: '',
- bulkUpdatePath: '',
- boardId: '',
- });
-
allBoardsResponse = Promise.resolve({
data: {
group: {
@@ -68,9 +63,6 @@ describe('BoardsSelector', () => {
data: recentBoards,
});
- boardsStore.allBoards = jest.fn(() => allBoardsResponse);
- boardsStore.recentBoards = jest.fn(() => recentBoardsResponse);
-
wrapper = mount(BoardsSelector, {
propsData: {
throttleDuration,
@@ -95,6 +87,10 @@ describe('BoardsSelector', () => {
},
mocks: { $apollo },
attachTo: document.body,
+ provide: {
+ fullPath: '',
+ recentBoardsEndpoint: `${TEST_HOST}/recent`,
+ },
});
wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
@@ -103,6 +99,8 @@ describe('BoardsSelector', () => {
});
});
+ mock.onGet(`${TEST_HOST}/recent`).replyOnce(200, recentBoards);
+
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown().vm.$emit('show');
});
@@ -110,6 +108,7 @@ describe('BoardsSelector', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ mock.restore();
});
describe('loading', () => {
@@ -133,7 +132,8 @@ describe('BoardsSelector', () => {
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
});
- it('hides loading spinner', () => {
+ it('hides loading spinner', async () => {
+ await wrapper.vm.$nextTick();
expect(getLoadingIcon().exists()).toBe(false);
});
diff --git a/spec/frontend/boards/components/issue_time_estimate_spec.js b/spec/frontend/boards/components/issue_time_estimate_spec.js
index 9ac8fae3fcc..2e253d24125 100644
--- a/spec/frontend/boards/components/issue_time_estimate_spec.js
+++ b/spec/frontend/boards/components/issue_time_estimate_spec.js
@@ -1,5 +1,5 @@
-import { config as vueConfig } from 'vue';
import { shallowMount } from '@vue/test-utils';
+import { config as vueConfig } from 'vue';
import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue';
describe('Issue Time Estimate component', () => {
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 de414bb929e..12e9a9ba365 100644
--- a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import BoardSidebarItem from '~/boards/components/sidebar/board_editable_item.vue';
describe('boards sidebar remove issue', () => {
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
index b034c8cb11d..7838b5a0b2f 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDatepicker } from '@gitlab/ui';
-import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
+import { shallowMount } from '@vue/test-utils';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
+import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
index 86895c648a4..bc7df1c76c6 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui';
-import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue';
+import { shallowMount } from '@vue/test-utils';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
+import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue';
import { createStore } from '~/boards/stores';
+import createFlash from '~/flash';
const TEST_TITLE = 'New issue title';
const TEST_ISSUE_A = {
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
index 2342caa9dfd..12b873ba7d8 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLabel } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import { labels as TEST_LABELS, mockIssue as TEST_ISSUE } from 'jest/boards/mock_data';
-import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
jest.mock('~/flash');
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
index 74d88d9f34c..8820ec7ae63 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
@@ -1,8 +1,8 @@
+import { GlLoadingIcon, GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlLoadingIcon } from '@gitlab/ui';
import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
-import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
+import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
@@ -20,7 +20,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
wrapper = null;
});
- const createWrapper = ({ milestone = null } = {}) => {
+ const createWrapper = ({ milestone = null, loading = false } = {}) => {
store = createStore();
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
store.state.activeId = TEST_ISSUE.id;
@@ -38,7 +38,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
},
mocks: {
$apollo: {
- loading: false,
+ loading,
},
},
});
@@ -46,10 +46,42 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]');
const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]');
const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]');
+ describe('when not editing', () => {
+ it('opens the milestone dropdown on clicking edit', async () => {
+ createWrapper();
+ wrapper.vm.$refs.dropdown.show = jest.fn();
+
+ await findBoardEditableItem().vm.$emit('open');
+
+ expect(wrapper.vm.$refs.dropdown.show).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when editing', () => {
+ beforeEach(() => {
+ createWrapper();
+ jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse');
+ });
+
+ it('collapses BoardEditableItem on clicking edit', async () => {
+ await findBoardEditableItem().vm.$emit('close');
+
+ expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
+ });
+
+ it('collapses BoardEditableItem on hiding dropdown', async () => {
+ await findDropdown().vm.$emit('hide');
+
+ expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
+ });
+ });
+
it('renders "None" when no milestone is selected', () => {
createWrapper();
@@ -63,12 +95,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
});
it('shows loader while Apollo is loading', async () => {
- createWrapper({ milestone: TEST_MILESTONE });
-
- expect(findLoader().exists()).toBe(false);
-
- wrapper.vm.$apollo.loading = true;
- await wrapper.vm.$nextTick();
+ createWrapper({ milestone: TEST_MILESTONE, loading: true });
expect(findLoader().exists()).toBe(true);
});
@@ -76,8 +103,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
it('shows message when error or no milestones found', async () => {
createWrapper();
- wrapper.setData({ milestones: [] });
- await wrapper.vm.$nextTick();
+ await wrapper.setData({ milestones: [] });
expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
});
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
index b1df0f2d771..3e6b0be0267 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
@@ -1,11 +1,11 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
-import * as types from '~/boards/stores/mutation_types';
import { createStore } from '~/boards/stores';
-import { mockActiveIssue } from '../../mock_data';
+import * as types from '~/boards/stores/mutation_types';
import createFlash from '~/flash';
+import { mockActiveIssue } from '../../mock_data';
jest.mock('~/flash.js');
diff --git a/spec/frontend/boards/components/sidebar/remove_issue_spec.js b/spec/frontend/boards/components/sidebar/remove_issue_spec.js
index 1b7a78e6e58..1f740c10106 100644
--- a/spec/frontend/boards/components/sidebar/remove_issue_spec.js
+++ b/spec/frontend/boards/components/sidebar/remove_issue_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import RemoveIssue from '~/boards/components/sidebar/remove_issue.vue';
diff --git a/spec/frontend/boards/issue_card_deprecated_spec.js b/spec/frontend/boards/issue_card_deprecated_spec.js
index fd7b0edb97e..909be275030 100644
--- a/spec/frontend/boards/issue_card_deprecated_spec.js
+++ b/spec/frontend/boards/issue_card_deprecated_spec.js
@@ -1,14 +1,14 @@
/* global ListAssignee, ListLabel, ListIssue */
+import { GlLabel } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { range } from 'lodash';
import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import { GlLabel } from '@gitlab/ui';
import IssueCardInner from '~/boards/components/issue_card_inner_deprecated.vue';
-import { listObj } from './mock_data';
import store from '~/boards/stores';
+import { listObj } from './mock_data';
describe('Issue card component', () => {
const user = new ListAssignee({
diff --git a/spec/frontend/boards/issue_card_inner_spec.js b/spec/frontend/boards/issue_card_inner_spec.js
index f9ad78494af..b9f84fed6b3 100644
--- a/spec/frontend/boards/issue_card_inner_spec.js
+++ b/spec/frontend/boards/issue_card_inner_spec.js
@@ -1,11 +1,11 @@
+import { GlLabel } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { range } from 'lodash';
-import { GlLabel } from '@gitlab/ui';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
-import { mockLabelList } from './mock_data';
-import defaultStore from '~/boards/stores';
import eventHub from '~/boards/eventhub';
+import defaultStore from '~/boards/stores';
import { updateHistory } from '~/lib/utils/url_utility';
+import { mockLabelList } from './mock_data';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/boards/eventhub');
diff --git a/spec/frontend/boards/issue_spec.js b/spec/frontend/boards/issue_spec.js
index d68e17c06a7..1f354fb04db 100644
--- a/spec/frontend/boards/issue_spec.js
+++ b/spec/frontend/boards/issue_spec.js
@@ -41,7 +41,7 @@ describe('Issue model', () => {
});
expect(issue.labels.length).toBe(1);
- expect(issue.labels[0].color).toBe('red');
+ expect(issue.labels[0].color).toBe('#F0AD4E');
});
it('adds other label with same title', () => {
diff --git a/spec/frontend/boards/list_spec.js b/spec/frontend/boards/list_spec.js
index db01f62c9a6..4d6a82bdff0 100644
--- a/spec/frontend/boards/list_spec.js
+++ b/spec/frontend/boards/list_spec.js
@@ -2,16 +2,15 @@
/* global ListAssignee */
/* global ListIssue */
/* global ListLabel */
-
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import { ListType } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
import { listObj, listObjDuplicate, boardsMockInterceptor } from './mock_data';
describe('List model', () => {
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index d5cfb9b7d07..e106b9235d6 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -1,7 +1,7 @@
/* global List */
-import Vue from 'vue';
import { keyBy } from 'lodash';
+import Vue from 'vue';
import '~/boards/models/list';
import boardsStore from '~/boards/stores/boards_store';
@@ -137,7 +137,7 @@ export const rawIssue = {
{
id: 1,
title: 'test',
- color: 'red',
+ color: '#F0AD4E',
description: 'testing',
},
],
@@ -165,7 +165,7 @@ export const mockIssue = {
{
id: 1,
title: 'test',
- color: 'red',
+ color: '#F0AD4E',
description: 'testing',
},
],
diff --git a/spec/frontend/boards/project_select_deprecated_spec.js b/spec/frontend/boards/project_select_deprecated_spec.js
index e4f8f96bd33..9042c4bf9ba 100644
--- a/spec/frontend/boards/project_select_deprecated_spec.js
+++ b/spec/frontend/boards/project_select_deprecated_spec.js
@@ -1,14 +1,13 @@
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
-import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
-import httpStatus from '~/lib/utils/http_status';
-import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
+import ProjectSelect from '~/boards/components/project_select_deprecated.vue';
import { ListType } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import { deprecatedCreateFlash as flash } from '~/flash';
-
-import ProjectSelect from '~/boards/components/project_select_deprecated.vue';
+import httpStatus from '~/lib/utils/http_status';
+import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import { listObj, mockRawGroupProjects } from './mock_data';
diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js
index 14ddab3542b..aa71952c42b 100644
--- a/spec/frontend/boards/project_select_spec.js
+++ b/spec/frontend/boards/project_select_spec.js
@@ -1,9 +1,8 @@
-import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
-import defaultState from '~/boards/stores/state';
-
+import { createLocalVue, mount } from '@vue/test-utils';
+import Vuex from 'vuex';
import ProjectSelect from '~/boards/components/project_select.vue';
+import defaultState from '~/boards/stores/state';
import { mockList, mockGroupProjects } from './mock_data';
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index e4209cd5e55..32d0e7ae886 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,5 +1,17 @@
import testAction from 'helpers/vuex_action_helper';
import {
+ fullBoardId,
+ formatListIssues,
+ formatBoardLists,
+ formatIssueInput,
+} from '~/boards/boards_util';
+import { inactiveId } from '~/boards/constants';
+import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
+import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql';
+import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
+import actions, { gqlClient } from '~/boards/stores/actions';
+import * as types from '~/boards/stores/mutation_types';
+import {
mockLists,
mockListsById,
mockIssue,
@@ -11,20 +23,6 @@ import {
mockActiveIssue,
mockGroupProjects,
} from '../mock_data';
-import actions, { gqlClient } from '~/boards/stores/actions';
-import * as types from '~/boards/stores/mutation_types';
-import { inactiveId } from '~/boards/constants';
-import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
-import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
-import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql';
-import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
-import {
- fullBoardId,
- formatListIssues,
- formatBoardLists,
- formatIssueInput,
-} from '~/boards/boards_util';
-import createFlash from '~/flash';
jest.mock('~/flash');
@@ -71,7 +69,7 @@ describe('setFilters', () => {
actions.setFilters,
filters,
state,
- [{ type: types.SET_FILTERS, payload: filters }],
+ [{ type: types.SET_FILTERS, payload: { ...filters, not: {} } }],
[],
done,
);
@@ -186,7 +184,27 @@ describe('fetchLists', () => {
});
describe('createList', () => {
- it('should dispatch addList action when creating backlog list', (done) => {
+ let commit;
+ let dispatch;
+ let getters;
+ let state;
+
+ beforeEach(() => {
+ state = {
+ fullPath: 'gitlab-org',
+ boardId: '1',
+ boardType: 'group',
+ disabled: false,
+ boardLists: [{ type: 'closed' }],
+ };
+ commit = jest.fn();
+ dispatch = jest.fn();
+ getters = {
+ getListByLabelId: jest.fn(),
+ };
+ });
+
+ it('should dispatch addList action when creating backlog list', async () => {
const backlogList = {
id: 'gid://gitlab/List/1',
listType: 'backlog',
@@ -205,25 +223,35 @@ describe('createList', () => {
}),
);
- const state = {
- fullPath: 'gitlab-org',
- boardId: '1',
- boardType: 'group',
- disabled: false,
- boardLists: [{ type: 'closed' }],
+ await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
+
+ expect(dispatch).toHaveBeenCalledWith('addList', backlogList);
+ });
+
+ it('dispatches highlightList after addList has succeeded', async () => {
+ const list = {
+ id: 'gid://gitlab/List/1',
+ listType: 'label',
+ title: 'Open',
+ labelId: '4',
};
- testAction(
- actions.createList,
- { backlog: true },
- state,
- [],
- [{ type: 'addList', payload: backlogList }],
- done,
- );
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ boardListCreate: {
+ list,
+ errors: [],
+ },
+ },
+ });
+
+ await actions.createList({ getters, state, commit, dispatch }, { labelId: '4' });
+
+ expect(dispatch).toHaveBeenCalledWith('addList', list);
+ expect(dispatch).toHaveBeenCalledWith('highlightList', list.id);
});
- it('should commit CREATE_LIST_FAILURE mutation when API returns an error', (done) => {
+ it('should commit CREATE_LIST_FAILURE mutation when API returns an error', async () => {
jest.spyOn(gqlClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
@@ -235,22 +263,49 @@ describe('createList', () => {
}),
);
- const state = {
- fullPath: 'gitlab-org',
- boardId: '1',
- boardType: 'group',
- disabled: false,
- boardLists: [{ type: 'closed' }],
+ await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
+
+ expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE);
+ });
+
+ it('highlights list and does not re-query if it already exists', async () => {
+ const existingList = {
+ id: 'gid://gitlab/List/1',
+ listType: 'label',
+ title: 'Some label',
+ position: 1,
};
- testAction(
- actions.createList,
- { backlog: true },
- state,
- [{ type: types.CREATE_LIST_FAILURE }],
- [],
- done,
- );
+ getters = {
+ getListByLabelId: jest.fn().mockReturnValue(existingList),
+ };
+
+ await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
+
+ expect(dispatch).toHaveBeenCalledWith('highlightList', existingList.id);
+ expect(dispatch).toHaveBeenCalledTimes(1);
+ expect(commit).not.toHaveBeenCalled();
+ });
+});
+
+describe('fetchLabels', () => {
+ it('should commit mutation RECEIVE_LABELS_SUCCESS on success', async () => {
+ const queryResponse = {
+ data: {
+ group: {
+ labels: {
+ nodes: labels,
+ },
+ },
+ },
+ };
+ jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
+
+ await testAction({
+ action: actions.fetchLabels,
+ state: { boardType: 'group' },
+ expectedMutations: [{ type: types.RECEIVE_LABELS_SUCCESS, payload: labels }],
+ });
});
});
@@ -669,65 +724,27 @@ describe('moveIssue', () => {
describe('setAssignees', () => {
const node = { username: 'name' };
- const name = 'username';
const projectPath = 'h/h';
const refPath = `${projectPath}#3`;
const iid = '1';
describe('when succeeds', () => {
- beforeEach(() => {
- jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
- data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } },
- });
- });
-
- it('calls mutate with the correct values', async () => {
- await actions.setAssignees(
- { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } },
- [name],
- );
-
- expect(gqlClient.mutate).toHaveBeenCalledWith({
- mutation: updateAssignees,
- variables: { iid, assigneeUsernames: [name], projectPath },
- });
- });
-
it('calls the correct mutation with the correct values', (done) => {
testAction(
actions.setAssignees,
- {},
+ [node],
{ activeIssue: { iid, referencePath: refPath }, commit: () => {} },
[
- { type: types.SET_ASSIGNEE_LOADING, payload: true },
{
type: 'UPDATE_ISSUE_BY_ID',
payload: { prop: 'assignees', issueId: undefined, value: [node] },
},
- { type: types.SET_ASSIGNEE_LOADING, payload: false },
],
[],
done,
);
});
});
-
- describe('when fails', () => {
- beforeEach(() => {
- jest.spyOn(gqlClient, 'mutate').mockRejectedValue();
- });
-
- it('calls createFlash', async () => {
- await actions.setAssignees({
- commit: () => {},
- getters: { activeIssue: { iid, referencePath: refPath } },
- });
-
- expect(createFlash).toHaveBeenCalledWith({
- message: 'An error occurred while updating assignees.',
- });
- });
- });
});
describe('createNewIssue', () => {
@@ -1201,6 +1218,40 @@ describe('setSelectedProject', () => {
});
});
+describe('toggleBoardItemMultiSelection', () => {
+ const boardItem = mockIssue;
+
+ it('should commit mutation ADD_BOARD_ITEM_TO_SELECTION if item is not on selection state', () => {
+ testAction(
+ actions.toggleBoardItemMultiSelection,
+ boardItem,
+ { selectedBoardItems: [] },
+ [
+ {
+ type: types.ADD_BOARD_ITEM_TO_SELECTION,
+ payload: boardItem,
+ },
+ ],
+ [],
+ );
+ });
+
+ it('should commit mutation REMOVE_BOARD_ITEM_FROM_SELECTION if item is on selection state', () => {
+ testAction(
+ actions.toggleBoardItemMultiSelection,
+ boardItem,
+ { selectedBoardItems: [mockIssue] },
+ [
+ {
+ type: types.REMOVE_BOARD_ITEM_FROM_SELECTION,
+ payload: boardItem,
+ },
+ ],
+ [],
+ );
+ });
+});
+
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 44b41b5667d..d5a19bf613f 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -1,5 +1,5 @@
-import getters from '~/boards/stores/getters';
import { inactiveId } from '~/boards/constants';
+import getters from '~/boards/stores/getters';
import {
mockIssue,
mockIssue2,
@@ -62,6 +62,22 @@ describe('Boards - Getters', () => {
});
});
+ describe('groupPathByIssueId', () => {
+ it('returns group path for the active issue', () => {
+ const mockActiveIssue = {
+ referencePath: 'gitlab-org/gitlab-test#1',
+ };
+ expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(
+ 'gitlab-org',
+ );
+ });
+
+ it('returns empty string as group path when active issue is an empty object', () => {
+ const mockActiveIssue = {};
+ expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual('');
+ });
+ });
+
describe('projectPathByIssueId', () => {
it('returns project path for the active issue', () => {
const mockActiveIssue = {
@@ -72,7 +88,7 @@ describe('Boards - Getters', () => {
);
});
- it('returns empty string as project when active issue is an empty object', () => {
+ it('returns empty string as project path when active issue is an empty object', () => {
const mockActiveIssue = {};
expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual('');
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index c5fe0e22c3c..9423f2ed583 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -1,7 +1,14 @@
-import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
+import mutations from '~/boards/stores/mutations';
import defaultState from '~/boards/stores/state';
-import { mockLists, rawIssue, mockIssue, mockIssue2, mockGroupProjects } from '../mock_data';
+import {
+ mockLists,
+ rawIssue,
+ mockIssue,
+ mockIssue2,
+ mockGroupProjects,
+ labels,
+} from '../mock_data';
const expectNotImplemented = (action) => {
it('is not implemented', () => {
@@ -99,13 +106,11 @@ describe('Board Store Mutations', () => {
});
});
- describe('RECEIVE_LABELS_FAILURE', () => {
- it('sets error message', () => {
- mutations.RECEIVE_LABELS_FAILURE(state);
+ describe('RECEIVE_LABELS_SUCCESS', () => {
+ it('sets labels on state', () => {
+ mutations.RECEIVE_LABELS_SUCCESS(state, labels);
- expect(state.error).toEqual(
- 'An error occurred while fetching labels. Please reload the page.',
- );
+ expect(state.labels).toEqual(labels);
});
});
@@ -589,4 +594,27 @@ describe('Board Store Mutations', () => {
expect(state.selectedProject).toEqual(mockGroupProjects[0]);
});
});
+
+ describe('ADD_BOARD_ITEM_TO_SELECTION', () => {
+ it('Should add boardItem to selectedBoardItems state', () => {
+ expect(state.selectedBoardItems).toEqual([]);
+
+ mutations[types.ADD_BOARD_ITEM_TO_SELECTION](state, mockIssue);
+
+ expect(state.selectedBoardItems).toEqual([mockIssue]);
+ });
+ });
+
+ describe('REMOVE_BOARD_ITEM_FROM_SELECTION', () => {
+ it('Should remove boardItem to selectedBoardItems state', () => {
+ state = {
+ ...state,
+ selectedBoardItems: [mockIssue],
+ };
+
+ mutations[types.REMOVE_BOARD_ITEM_FROM_SELECTION](state, mockIssue);
+
+ expect(state.selectedBoardItems).toEqual([]);
+ });
+ });
});
diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js
index adf39a2216a..be97a1724d3 100644
--- a/spec/frontend/branches/divergence_graph_spec.js
+++ b/spec/frontend/branches/divergence_graph_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import init from '~/branches/divergence_graph';
+import axios from '~/lib/utils/axios_utils';
describe('Divergence graph', () => {
let mock;
diff --git a/spec/frontend/captcha/captcha_modal_spec.js b/spec/frontend/captcha/captcha_modal_spec.js
new file mode 100644
index 00000000000..b8448f9ff0a
--- /dev/null
+++ b/spec/frontend/captcha/captcha_modal_spec.js
@@ -0,0 +1,171 @@
+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';
+
+jest.mock('~/captcha/init_recaptcha_script');
+
+describe('Captcha Modal', () => {
+ let wrapper;
+ let modal;
+ let grecaptcha;
+
+ const captchaSiteKey = 'abc123';
+
+ function createComponent({ props = {} } = {}) {
+ wrapper = shallowMount(CaptchaModal, {
+ propsData: {
+ captchaSiteKey,
+ ...props,
+ },
+ stubs: {
+ GlModal: stubComponent(GlModal),
+ },
+ });
+ }
+
+ beforeEach(() => {
+ grecaptcha = {
+ render: jest.fn(),
+ };
+
+ initRecaptchaScript.mockResolvedValue(grecaptcha);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findGlModal = () => {
+ const glModal = wrapper.find(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', () => {
+ it('renders', () => {
+ expect(modal.exists()).toBe(true);
+ });
+
+ it('assigns the modal a unique ID', () => {
+ const firstInstanceModalId = modal.props('modalId');
+ createComponent();
+ const secondInstanceModalId = findGlModal().props('modalId');
+ expect(firstInstanceModalId).not.toEqual(secondInstanceModalId);
+ });
+ });
+
+ describe('functionality', () => {
+ describe('when modal is shown', () => {
+ describe('when initRecaptchaScript promise resolves successfully', () => {
+ beforeEach(async () => {
+ showModal();
+
+ await nextTick();
+ });
+
+ it('shows modal', async () => {
+ expect(findGlModal().vm.show).toHaveBeenCalled();
+ });
+
+ it('renders window.grecaptcha', () => {
+ expect(grecaptcha.render).toHaveBeenCalledWith(wrapper.vm.$refs.captcha, {
+ sitekey: captchaSiteKey,
+ callback: expect.any(Function),
+ });
+ });
+
+ describe('then the user solves the captcha', () => {
+ const captchaResponse = 'a captcha response';
+
+ beforeEach(() => {
+ // simulate the grecaptcha library invoking the callback
+ const { callback } = grecaptcha.render.mock.calls[0][1];
+ callback(captchaResponse);
+ });
+
+ it('emits receivedCaptchaResponse exactly once with the captcha response', () => {
+ expect(wrapper.emitted('receivedCaptchaResponse')).toEqual([[captchaResponse]]);
+ });
+
+ 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();
+ });
+ });
+
+ describe('then the user hides the modal without solving the captcha', () => {
+ // Even though we don't explicitly check for these trigger values, these are the
+ // currently supported ones which can be emitted.
+ // See https://bootstrap-vue.org/docs/components/modal#prevent-closing
+ describe.each`
+ trigger | expected
+ ${'cancel'} | ${[[null]]}
+ ${'esc'} | ${[[null]]}
+ ${'backdrop'} | ${[[null]]}
+ ${'headerclose'} | ${[[null]]}
+ `('using the $trigger trigger', ({ trigger, expected }) => {
+ beforeEach(() => {
+ const bvModalEvent = {
+ trigger,
+ };
+ modal.vm.$emit('hide', bvModalEvent);
+ });
+
+ it(`emits receivedCaptchaResponse with ${JSON.stringify(expected)}`, () => {
+ expect(wrapper.emitted('receivedCaptchaResponse')).toEqual(expected);
+ });
+ });
+ });
+ });
+
+ describe('when initRecaptchaScript promise rejects', () => {
+ const fakeError = {};
+
+ beforeEach(() => {
+ initRecaptchaScript.mockImplementation(() => Promise.reject(fakeError));
+
+ jest.spyOn(console, 'error').mockImplementation();
+
+ showModal();
+ });
+
+ it('emits receivedCaptchaResponse exactly once with null', () => {
+ expect(wrapper.emitted('receivedCaptchaResponse')).toEqual([[null]]);
+ });
+
+ 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();
+ });
+
+ it('calls console.error with a message and the exception', () => {
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledWith(
+ expect.stringMatching(/exception.*captcha/),
+ fakeError,
+ );
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/captcha/init_recaptcha_script_spec.js b/spec/frontend/captcha/init_recaptcha_script_spec.js
new file mode 100644
index 00000000000..af07c9e474e
--- /dev/null
+++ b/spec/frontend/captcha/init_recaptcha_script_spec.js
@@ -0,0 +1,59 @@
+import {
+ RECAPTCHA_API_URL_PREFIX,
+ RECAPTCHA_ONLOAD_CALLBACK_NAME,
+ clearMemoizeCache,
+ initRecaptchaScript,
+} from '~/captcha/init_recaptcha_script';
+
+describe('initRecaptchaScript', () => {
+ afterEach(() => {
+ document.head.innerHTML = '';
+ clearMemoizeCache();
+ });
+
+ const getScriptOnload = () => window[RECAPTCHA_ONLOAD_CALLBACK_NAME];
+ const triggerScriptOnload = () => window[RECAPTCHA_ONLOAD_CALLBACK_NAME]();
+
+ describe('when called', () => {
+ let result;
+
+ beforeEach(() => {
+ result = initRecaptchaScript();
+ });
+
+ it('adds script to head', () => {
+ expect(document.head).toMatchInlineSnapshot(`
+ <head>
+ <script
+ class="js-recaptcha-script"
+ src="${RECAPTCHA_API_URL_PREFIX}?onload=${RECAPTCHA_ONLOAD_CALLBACK_NAME}&render=explicit"
+ />
+ </head>
+ `);
+ });
+
+ it('is memoized', () => {
+ expect(initRecaptchaScript()).toBe(result);
+ expect(document.head.querySelectorAll('script').length).toBe(1);
+ });
+
+ describe('when onload is triggered', () => {
+ beforeEach(() => {
+ window.grecaptcha = 'fake grecaptcha';
+ triggerScriptOnload();
+ });
+
+ afterEach(() => {
+ window.grecaptcha = undefined;
+ });
+
+ it('resolves promise with window.grecaptcha as argument', async () => {
+ await expect(result).resolves.toBe(window.grecaptcha);
+ });
+
+ it('sets window[RECAPTCHA_ONLOAD_CALLBACK_NAME] to undefined', async () => {
+ expect(getScriptOnload()).toBeUndefined();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci_lint/components/ci_lint_spec.js b/spec/frontend/ci_lint/components/ci_lint_spec.js
index 1c99fdb3505..8a065436da0 100644
--- a/spec/frontend/ci_lint/components/ci_lint_spec.js
+++ b/spec/frontend/ci_lint/components/ci_lint_spec.js
@@ -1,10 +1,10 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
-import EditorLite from '~/vue_shared/components/editor_lite.vue';
import CiLint from '~/ci_lint/components/ci_lint.vue';
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
import lintCIMutation from '~/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql';
+import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { mockLintDataValid } from '../mock_data';
describe('CI Lint', () => {
diff --git a/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js b/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js
index 30aa634fc81..41af257ad89 100644
--- a/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js
+++ b/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js
@@ -1,9 +1,9 @@
-import { mount } from '@vue/test-utils';
import { GlTable, GlBadge } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import TriggersList from '~/ci_settings_pipeline_triggers/components/triggers_list.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import TriggersList from '~/ci_settings_pipeline_triggers/components/triggers_list.vue';
import { triggers } from '../mock_data';
describe('TriggersList', () => {
diff --git a/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
index faa88394447..75c6e8e4540 100644
--- a/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlDropdownItem, GlIcon } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
const localVue = createLocalVue();
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
index b2e51029a06..991dc8592e9 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
-import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants';
+import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import Vuex from 'vuex';
import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
+import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants';
import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data';
import ModalStub from '../stubs';
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js
index 5d37f059161..b43153d3d7c 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import CiVariablePopover from '~/ci_variable_list/components/ci_variable_popover.vue';
import mockData from '../services/mock_data';
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
index 12449fc7615..03f90f72d87 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import CiVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue';
import createStore from '~/ci_variable_list/store';
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
index fbc34528d4d..ade2d65b857 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
import { GlTable } from '@gitlab/ui';
+import { createLocalVue, mount } from '@vue/test-utils';
+import Vuex from 'vuex';
import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data';
diff --git a/spec/frontend/ci_variable_list/store/actions_spec.js b/spec/frontend/ci_variable_list/store/actions_spec.js
index 075e5829305..be3640936dc 100644
--- a/spec/frontend/ci_variable_list/store/actions_spec.js
+++ b/spec/frontend/ci_variable_list/store/actions_spec.js
@@ -1,13 +1,13 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import getInitialState from '~/ci_variable_list/store/state';
import * as actions from '~/ci_variable_list/store/actions';
import * as types from '~/ci_variable_list/store/mutation_types';
-import mockData from '../services/mock_data';
+import getInitialState from '~/ci_variable_list/store/state';
import { prepareDataForDisplay, prepareEnvironments } from '~/ci_variable_list/store/utils';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import mockData from '../services/mock_data';
jest.mock('~/api.js');
jest.mock('~/flash.js');
diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js
index a333fb7d8f9..ae750ff426d 100644
--- a/spec/frontend/ci_variable_list/store/mutations_spec.js
+++ b/spec/frontend/ci_variable_list/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/ci_variable_list/store/state';
-import mutations from '~/ci_variable_list/store/mutations';
import * as types from '~/ci_variable_list/store/mutation_types';
+import mutations from '~/ci_variable_list/store/mutations';
+import state from '~/ci_variable_list/store/state';
describe('CI variable list mutations', () => {
let stateCopy;
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 ee4ec4636ea..6047b404197 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
@@ -60,8 +60,8 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
>
<svg
aria-hidden="true"
- class="gl-icon s16 gl-new-dropdown-item-check-icon"
- data-testid="mobile-issue-close-icon"
+ class="gl-icon s16 gl-new-dropdown-item-check-icon gl-mt-3 gl-align-self-start"
+ data-testid="dropdown-item-checkbox"
>
<use
href="#mobile-issue-close"
@@ -115,8 +115,8 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
>
<svg
aria-hidden="true"
- class="gl-icon s16 gl-new-dropdown-item-check-icon gl-visibility-hidden"
- data-testid="mobile-issue-close-icon"
+ class="gl-icon s16 gl-new-dropdown-item-check-icon gl-visibility-hidden gl-mt-3 gl-align-self-start"
+ data-testid="dropdown-item-checkbox"
>
<use
href="#mobile-issue-close"
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
index 49a299cfb3c..eff3493d7bd 100644
--- a/spec/frontend/clusters/components/application_row_spec.js
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -1,10 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
-import eventHub from '~/clusters/event_hub';
-import { APPLICATION_STATUS, ELASTIC_STACK } from '~/clusters/constants';
+import { shallowMount } from '@vue/test-utils';
import ApplicationRow from '~/clusters/components/application_row.vue';
import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue';
import UpdateApplicationConfirmationModal from '~/clusters/components/update_application_confirmation_modal.vue';
+import { APPLICATION_STATUS, ELASTIC_STACK } from '~/clusters/constants';
+import eventHub from '~/clusters/event_hub';
import { DEFAULT_APPLICATION_STATE } from '../services/mock_data';
diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js
index cf89246c1a5..db5915cb1eb 100644
--- a/spec/frontend/clusters/components/applications_spec.js
+++ b/spec/frontend/clusters/components/applications_spec.js
@@ -1,13 +1,13 @@
import { shallowMount, mount } from '@vue/test-utils';
-import Applications from '~/clusters/components/applications.vue';
-import { CLUSTER_TYPE, PROVIDER_TYPE } from '~/clusters/constants';
-import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
-import eventHub from '~/clusters/event_hub';
import ApplicationRow from '~/clusters/components/application_row.vue';
-import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
+import Applications from '~/clusters/components/applications.vue';
import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue';
-import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
import FluentdOutputSettings from '~/clusters/components/fluentd_output_settings.vue';
+import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
+import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
+import { CLUSTER_TYPE, PROVIDER_TYPE } from '~/clusters/constants';
+import eventHub from '~/clusters/event_hub';
+import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
describe('Applications', () => {
let wrapper;
@@ -16,7 +16,7 @@ describe('Applications', () => {
gon.features = gon.features || {};
});
- const createApp = ({ applications, type, propsData } = {}, isShallow) => {
+ const createComponent = ({ applications, type, propsData } = {}, isShallow) => {
const mountMethod = isShallow ? shallowMount : mount;
wrapper = mountMethod(Applications, {
@@ -29,7 +29,7 @@ describe('Applications', () => {
});
};
- const createShallowApp = (options) => createApp(options, true);
+ const createShallowComponent = (options) => createComponent(options, true);
const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`);
afterEach(() => {
wrapper.destroy();
@@ -37,7 +37,7 @@ describe('Applications', () => {
describe('Project cluster applications', () => {
beforeEach(() => {
- createApp({ type: CLUSTER_TYPE.PROJECT });
+ createComponent({ type: CLUSTER_TYPE.PROJECT });
});
it('renders a row for Ingress', () => {
@@ -82,7 +82,7 @@ describe('Applications', () => {
describe('Group cluster applications', () => {
beforeEach(() => {
- createApp({ type: CLUSTER_TYPE.GROUP });
+ createComponent({ type: CLUSTER_TYPE.GROUP });
});
it('renders a row for Ingress', () => {
@@ -128,7 +128,7 @@ describe('Applications', () => {
describe('Instance cluster applications', () => {
beforeEach(() => {
- createApp({ type: CLUSTER_TYPE.INSTANCE });
+ createComponent({ type: CLUSTER_TYPE.INSTANCE });
});
it('renders a row for Ingress', () => {
@@ -174,14 +174,14 @@ describe('Applications', () => {
describe('Helm application', () => {
it('does not render a row for Helm Tiller', () => {
- createApp();
+ createComponent();
expect(wrapper.find('.js-cluster-application-row-helm').exists()).toBe(false);
});
});
describe('Ingress application', () => {
it('shows the correct warning message', () => {
- createApp();
+ createComponent();
expect(findByTestId('ingressCostWarning').element).toMatchSnapshot();
});
@@ -195,7 +195,7 @@ describe('Applications', () => {
},
};
- beforeEach(() => createShallowApp(propsData));
+ beforeEach(() => createShallowComponent(propsData));
it('renders IngressModsecuritySettings', () => {
const modsecuritySettings = wrapper.find(IngressModsecuritySettings);
@@ -206,7 +206,7 @@ describe('Applications', () => {
describe('when installed', () => {
describe('with ip address', () => {
it('renders ip address with a clipboard button', () => {
- createApp({
+ createComponent({
applications: {
ingress: {
title: 'Ingress',
@@ -225,7 +225,7 @@ describe('Applications', () => {
describe('with hostname', () => {
it('renders hostname with a clipboard button', () => {
- createApp({
+ createComponent({
applications: {
ingress: {
title: 'Ingress',
@@ -255,7 +255,7 @@ describe('Applications', () => {
describe('without ip address', () => {
it('renders an input text with a loading icon and an alert text', () => {
- createApp({
+ createComponent({
applications: {
ingress: {
title: 'Ingress',
@@ -272,7 +272,7 @@ describe('Applications', () => {
describe('before installing', () => {
it('does not render the IP address', () => {
- createApp();
+ createComponent();
expect(wrapper.text()).not.toContain('Ingress IP Address');
expect(wrapper.find('.js-endpoint').exists()).toBe(false);
@@ -282,13 +282,13 @@ describe('Applications', () => {
describe('Cert-Manager application', () => {
it('shows the correct description', () => {
- createApp();
+ createComponent();
expect(findByTestId('certManagerDescription').element).toMatchSnapshot();
});
describe('when not installed', () => {
it('renders email & allows editing', () => {
- createApp({
+ createComponent({
applications: {
cert_manager: {
title: 'Cert-Manager',
@@ -305,7 +305,7 @@ describe('Applications', () => {
describe('when installed', () => {
it('renders email in readonly', () => {
- createApp({
+ createComponent({
applications: {
cert_manager: {
title: 'Cert-Manager',
@@ -324,7 +324,7 @@ describe('Applications', () => {
describe('Jupyter application', () => {
describe('with ingress installed with ip & jupyter installable', () => {
it('renders hostname active input', () => {
- createApp({
+ createComponent({
applications: {
ingress: {
title: 'Ingress',
@@ -342,7 +342,7 @@ describe('Applications', () => {
describe('with ingress installed without external ip', () => {
it('does not render hostname input', () => {
- createApp({
+ createComponent({
applications: {
ingress: { title: 'Ingress', status: 'installed' },
},
@@ -356,7 +356,7 @@ describe('Applications', () => {
describe('with ingress & jupyter installed', () => {
it('renders readonly input', () => {
- createApp({
+ createComponent({
applications: {
ingress: {
title: 'Ingress',
@@ -375,7 +375,7 @@ describe('Applications', () => {
describe('without ingress installed', () => {
beforeEach(() => {
- createApp();
+ createComponent();
});
it('does not render input', () => {
@@ -388,7 +388,7 @@ describe('Applications', () => {
describe('Prometheus application', () => {
it('shows the correct description', () => {
- createApp();
+ createComponent();
expect(findByTestId('prometheusDescription').element).toMatchSnapshot();
});
});
@@ -414,14 +414,14 @@ describe('Applications', () => {
let knativeDomainEditor;
beforeEach(() => {
- createShallowApp(propsData);
+ createShallowComponent(propsData);
jest.spyOn(eventHub, '$emit');
knativeDomainEditor = wrapper.find(KnativeDomainEditor);
});
it('shows the correct description', async () => {
- createApp();
+ createComponent();
wrapper.setProps({
providerType: PROVIDER_TYPE.GCP,
preInstalledKnative: true,
@@ -487,7 +487,7 @@ describe('Applications', () => {
},
};
- beforeEach(() => createShallowApp(propsData));
+ beforeEach(() => createShallowComponent(propsData));
it('renders the correct Component', () => {
const crossplane = wrapper.find(CrossplaneProviderStack);
@@ -495,7 +495,7 @@ describe('Applications', () => {
});
it('shows the correct description', () => {
- createApp();
+ createComponent();
expect(findByTestId('crossplaneDescription').element).toMatchSnapshot();
});
});
@@ -503,7 +503,7 @@ describe('Applications', () => {
describe('Elastic Stack application', () => {
describe('with elastic stack installable', () => {
it('renders the install button enabled', () => {
- createApp();
+ createComponent();
expect(
wrapper
@@ -517,7 +517,7 @@ describe('Applications', () => {
describe('elastic stack installed', () => {
it('renders uninstall button', () => {
- createApp({
+ createComponent({
applications: {
elastic_stack: { title: 'Elastic Stack', status: 'installed' },
},
@@ -535,7 +535,7 @@ describe('Applications', () => {
});
describe('Fluentd application', () => {
- beforeEach(() => createShallowApp());
+ beforeEach(() => createShallowComponent());
it('renders the correct Component', () => {
expect(wrapper.find(FluentdOutputSettings).exists()).toBe(true);
@@ -544,7 +544,7 @@ describe('Applications', () => {
describe('Cilium application', () => {
it('shows the correct description', () => {
- createApp({ propsData: { ciliumHelpPath: 'cilium-help-path' } });
+ createComponent({ propsData: { ciliumHelpPath: 'cilium-help-path' } });
expect(findByTestId('ciliumDescription').element).toMatchSnapshot();
});
});
diff --git a/spec/frontend/clusters/components/fluentd_output_settings_spec.js b/spec/frontend/clusters/components/fluentd_output_settings_spec.js
index cd996ae915b..2c6e5bbd46a 100644
--- a/spec/frontend/clusters/components/fluentd_output_settings_spec.js
+++ b/spec/frontend/clusters/components/fluentd_output_settings_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlDropdown, GlFormCheckbox } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import FluentdOutputSettings from '~/clusters/components/fluentd_output_settings.vue';
import { APPLICATION_STATUS, FLUENTD } from '~/clusters/constants';
import eventHub from '~/clusters/event_hub';
diff --git a/spec/frontend/clusters/components/ingress_modsecurity_settings_spec.js b/spec/frontend/clusters/components/ingress_modsecurity_settings_spec.js
index 1f07a0b7908..fd6d9854868 100644
--- a/spec/frontend/clusters/components/ingress_modsecurity_settings_spec.js
+++ b/spec/frontend/clusters/components/ingress_modsecurity_settings_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlToggle, GlDropdown } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import eventHub from '~/clusters/event_hub';
diff --git a/spec/frontend/clusters/components/knative_domain_editor_spec.js b/spec/frontend/clusters/components/knative_domain_editor_spec.js
index b7f76211fd6..207eb071171 100644
--- a/spec/frontend/clusters/components/knative_domain_editor_spec.js
+++ b/spec/frontend/clusters/components/knative_domain_editor_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
import { APPLICATION_STATUS } from '~/clusters/constants';
diff --git a/spec/frontend/clusters/components/new_cluster_spec.js b/spec/frontend/clusters/components/new_cluster_spec.js
index bb4898f98ba..e4bca5eaaa5 100644
--- a/spec/frontend/clusters/components/new_cluster_spec.js
+++ b/spec/frontend/clusters/components/new_cluster_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import NewCluster from '~/clusters/components/new_cluster.vue';
import createClusterStore from '~/clusters/stores/new_cluster';
diff --git a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
index f448948843a..e2726b93ea5 100644
--- a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
+++ b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
@@ -1,7 +1,7 @@
-import { mount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
-import SplitButton from '~/vue_shared/components/split_button.vue';
+import { mount } from '@vue/test-utils';
import RemoveClusterConfirmation from '~/clusters/components/remove_cluster_confirmation.vue';
+import SplitButton from '~/vue_shared/components/split_button.vue';
describe('Remove cluster confirmation modal', () => {
let wrapper;
diff --git a/spec/frontend/clusters/components/uninstall_application_button_spec.js b/spec/frontend/clusters/components/uninstall_application_button_spec.js
index c106292965e..2596820e5ac 100644
--- a/spec/frontend/clusters/components/uninstall_application_button_spec.js
+++ b/spec/frontend/clusters/components/uninstall_application_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UninstallApplicationButton from '~/clusters/components/uninstall_application_button.vue';
import { APPLICATION_STATUS } from '~/clusters/constants';
diff --git a/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js
index c07f6851826..74ae4ecc486 100644
--- a/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js
+++ b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue';
import { INGRESS } from '~/clusters/constants';
diff --git a/spec/frontend/clusters/components/update_application_confirmation_modal_spec.js b/spec/frontend/clusters/components/update_application_confirmation_modal_spec.js
index dd3aaf6f946..e933f17a980 100644
--- a/spec/frontend/clusters/components/update_application_confirmation_modal_spec.js
+++ b/spec/frontend/clusters/components/update_application_confirmation_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UpdateApplicationConfirmationModal from '~/clusters/components/update_application_confirmation_modal.vue';
import { ELASTIC_STACK } from '~/clusters/constants';
diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js
index 3a3700eb0b7..0323245244d 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 Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlToggle, GlButton } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import IntegrationForm from '~/clusters/forms/components/integration_form.vue';
import { createStore } from '~/clusters/forms/stores/index';
diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js
index a3850a64f3b..55230625ba4 100644
--- a/spec/frontend/clusters/services/application_state_machine_spec.js
+++ b/spec/frontend/clusters/services/application_state_machine_spec.js
@@ -1,10 +1,10 @@
-import transitionApplicationState from '~/clusters/services/application_state_machine';
import {
APPLICATION_STATUS,
UNINSTALL_EVENT,
UPDATE_EVENT,
INSTALL_EVENT,
} from '~/clusters/constants';
+import transitionApplicationState from '~/clusters/services/application_state_machine';
const {
NO_STATUS,
diff --git a/spec/frontend/clusters/services/crossplane_provider_stack_spec.js b/spec/frontend/clusters/services/crossplane_provider_stack_spec.js
index 3e5f8de8e7b..f95b175ca64 100644
--- a/spec/frontend/clusters/services/crossplane_provider_stack_spec.js
+++ b/spec/frontend/clusters/services/crossplane_provider_stack_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue';
describe('CrossplaneProviderStack component', () => {
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index df10d9af273..c80949531c8 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -1,5 +1,5 @@
-import ClustersStore from '~/clusters/stores/clusters_store';
import { APPLICATION_INSTALLED_STATUSES, APPLICATION_STATUS, RUNNER } from '~/clusters/constants';
+import ClustersStore from '~/clusters/stores/clusters_store';
import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
describe('Clusters Store', () => {
diff --git a/spec/frontend/clusters_list/components/ancestor_notice_spec.js b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
index 79f917d4601..c7ee2a00f5b 100644
--- a/spec/frontend/clusters_list/components/ancestor_notice_spec.js
+++ b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import AncestorNotice from '~/clusters_list/components/ancestor_notice.vue';
import ClusterStore from '~/clusters_list/store';
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index d61f79071d5..f398d7a0965 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -1,15 +1,15 @@
-import MockAdapter from 'axios-mock-adapter';
-import { mount } from '@vue/test-utils';
import {
GlLoadingIcon,
GlPagination,
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlTable,
} from '@gitlab/ui';
-import * as Sentry from '~/sentry/wrapper';
-import axios from '~/lib/utils/axios_utils';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
import Clusters from '~/clusters_list/components/clusters.vue';
import ClusterStore from '~/clusters_list/store';
+import axios from '~/lib/utils/axios_utils';
+import * as Sentry from '~/sentry/wrapper';
import { apiData } from '../mock_data';
describe('Clusters', () => {
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 fa566c2dc57..18d27f3fd80 100644
--- a/spec/frontend/clusters_list/components/node_error_help_text_spec.js
+++ b/spec/frontend/clusters_list/components/node_error_help_text_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlPopover } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import NodeErrorHelpText from '~/clusters_list/components/node_error_help_text.vue';
describe('NodeErrorHelpText', () => {
diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js
index 6214cb50e13..00b998166aa 100644
--- a/spec/frontend/clusters_list/store/actions_spec.js
+++ b/spec/frontend/clusters_list/store/actions_spec.js
@@ -1,14 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import * as Sentry from '~/sentry/wrapper';
-import Poll from '~/lib/utils/poll';
+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 { deprecatedCreateFlash as flashError } from '~/flash';
import axios from '~/lib/utils/axios_utils';
+import Poll from '~/lib/utils/poll';
+import * as Sentry from '~/sentry/wrapper';
import { apiData } from '../mock_data';
-import { MAX_REQUESTS } from '~/clusters_list/constants';
-import * as types from '~/clusters_list/store/mutation_types';
-import * as actions from '~/clusters_list/store/actions';
jest.mock('~/flash.js');
diff --git a/spec/frontend/clusters_list/store/mutations_spec.js b/spec/frontend/clusters_list/store/mutations_spec.js
index df0dfe587b6..c0fe634a703 100644
--- a/spec/frontend/clusters_list/store/mutations_spec.js
+++ b/spec/frontend/clusters_list/store/mutations_spec.js
@@ -1,7 +1,7 @@
import * as types from '~/clusters_list/store/mutation_types';
-import { apiData } from '../mock_data';
-import getInitialState from '~/clusters_list/store/state';
import mutations from '~/clusters_list/store/mutations';
+import getInitialState from '~/clusters_list/store/state';
+import { apiData } from '../mock_data';
describe('Admin statistics panel mutations', () => {
let state;
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index 6dfc81dcc40..ea389fa35c0 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import createState from '~/code_navigation/store/state';
+import Vuex from 'vuex';
import App from '~/code_navigation/components/app.vue';
import Popover from '~/code_navigation/components/popover.vue';
+import createState from '~/code_navigation/store/state';
const localVue = createLocalVue();
const fetchData = jest.fn();
diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js
index 7b323cfab72..c038c04a0f8 100644
--- a/spec/frontend/code_navigation/components/popover_spec.js
+++ b/spec/frontend/code_navigation/components/popover_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import Popover from '~/code_navigation/components/popover.vue';
import DocLine from '~/code_navigation/components/doc_line.vue';
+import Popover from '~/code_navigation/components/popover.vue';
const DEFINITION_PATH_PREFIX = 'http://gitlab.com';
diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js
index 39cf4acd5ce..73f935deeca 100644
--- a/spec/frontend/code_navigation/store/actions_spec.js
+++ b/spec/frontend/code_navigation/store/actions_spec.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import actions from '~/code_navigation/store/actions';
-import axios from '~/lib/utils/axios_utils';
import { setCurrentHoverElement, addInteractionClass } from '~/code_navigation/utils';
+import axios from '~/lib/utils/axios_utils';
jest.mock('~/code_navigation/utils');
diff --git a/spec/frontend/collapsed_sidebar_todo_spec.js b/spec/frontend/collapsed_sidebar_todo_spec.js
index cc89a3c68f0..ef53cc9e103 100644
--- a/spec/frontend/collapsed_sidebar_todo_spec.js
+++ b/spec/frontend/collapsed_sidebar_todo_spec.js
@@ -1,10 +1,13 @@
/* eslint-disable no-new */
-import { clone } from 'lodash';
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
+import { clone } from 'lodash';
import waitForPromises from 'helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar';
+import { fixTitle } from '~/tooltips';
+
+jest.mock('~/tooltips');
describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html';
@@ -96,11 +99,10 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setImmediate(() => {
- expect(
- document
- .querySelector('.js-issuable-todo.sidebar-collapsed-icon')
- .getAttribute('data-original-title'),
- ).toBe('Mark as done');
+ const el = document.querySelector('.js-issuable-todo.sidebar-collapsed-icon');
+
+ expect(el.getAttribute('title')).toBe('Mark as done');
+ expect(fixTitle).toHaveBeenCalledWith(el);
done();
});
diff --git a/spec/frontend/commit/commit_pipeline_status_component_spec.js b/spec/frontend/commit/commit_pipeline_status_component_spec.js
index 6e4368b5de8..a56f761269a 100644
--- a/spec/frontend/commit/commit_pipeline_status_component_spec.js
+++ b/spec/frontend/commit/commit_pipeline_status_component_spec.js
@@ -1,11 +1,11 @@
-import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import Visibility from 'visibilityjs';
import { getJSONFixture } from 'helpers/fixtures';
-import Poll from '~/lib/utils/poll';
import { deprecatedCreateFlash as flash } from '~/flash';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
+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');
@@ -141,8 +141,8 @@ describe('Commit pipeline status component', () => {
expect(findLink().attributes('href')).toEqual(mockCiStatus.details_path);
});
- it('renders CI icon', () => {
- expect(findCiIcon().attributes('title')).toEqual('Pipeline: pending');
+ it('renders CI icon with the correct title and status', () => {
+ expect(findCiIcon().attributes('title')).toEqual('Pipeline: passed');
expect(findCiIcon().props('status')).toEqual(mockCiStatus);
});
});
diff --git a/spec/frontend/commit/pipelines/pipelines_spec.js b/spec/frontend/commit/pipelines/pipelines_spec.js
index 15b1f224699..f8bdd00f5da 100644
--- a/spec/frontend/commit/pipelines/pipelines_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_spec.js
@@ -1,9 +1,9 @@
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
-import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
+import axios from '~/lib/utils/axios_utils';
describe('Pipelines table in Commits and Merge requests', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
diff --git a/spec/frontend/commits_spec.js b/spec/frontend/commits_spec.js
index e1b947ddd0d..954025091cf 100644
--- a/spec/frontend/commits_spec.js
+++ b/spec/frontend/commits_spec.js
@@ -1,8 +1,8 @@
+import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import 'vendor/jquery.endless-scroll';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import CommitsList from '~/commits';
+import axios from '~/lib/utils/axios_utils';
import Pager from '~/pager';
describe('Commits List', () => {
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
index c441668f7c7..8f974051232 100644
--- a/spec/frontend/commons/nav/user_merge_requests_spec.js
+++ b/spec/frontend/commons/nav/user_merge_requests_spec.js
@@ -1,9 +1,9 @@
+import * as UserApi from '~/api/user_api';
import {
openUserCountsBroadcast,
closeUserCountsBroadcast,
refreshUserMergeRequestCounts,
} from '~/commons/nav/user_merge_requests';
-import * as UserApi from '~/api/user_api';
jest.mock('~/api');
diff --git a/spec/frontend/confidential_merge_request/components/dropdown_spec.js b/spec/frontend/confidential_merge_request/components/dropdown_spec.js
index 401948e24e4..14a0b98a0d5 100644
--- a/spec/frontend/confidential_merge_request/components/dropdown_spec.js
+++ b/spec/frontend/confidential_merge_request/components/dropdown_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import Dropdown from '~/confidential_merge_request/components/dropdown.vue';
let vm;
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 975701ebd96..67f6d360f52 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
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue';
+import axios from '~/lib/utils/axios_utils';
const mockData = [
{
diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js
index 24816e4e8ac..de55be4aa72 100644
--- a/spec/frontend/contributors/component/contributors_spec.js
+++ b/spec/frontend/contributors/component/contributors_spec.js
@@ -1,9 +1,9 @@
-import Vue from 'vue';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
+import ContributorsCharts from '~/contributors/components/contributors.vue';
import { createStore } from '~/contributors/stores';
import axios from '~/lib/utils/axios_utils';
-import ContributorsCharts from '~/contributors/components/contributors.vue';
let wrapper;
let mock;
diff --git a/spec/frontend/contributors/store/actions_spec.js b/spec/frontend/contributors/store/actions_spec.js
index 7d2f93c4940..82b6492b779 100644
--- a/spec/frontend/contributors/store/actions_spec.js
+++ b/spec/frontend/contributors/store/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as flashError } from '~/flash';
import * as actions from '~/contributors/stores/actions';
import * as types from '~/contributors/stores/mutation_types';
+import { deprecatedCreateFlash as flashError } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
jest.mock('~/flash.js');
diff --git a/spec/frontend/contributors/store/mutations_spec.js b/spec/frontend/contributors/store/mutations_spec.js
index e9e756d4a65..e8da1a48c5c 100644
--- a/spec/frontend/contributors/store/mutations_spec.js
+++ b/spec/frontend/contributors/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/contributors/stores/state';
-import mutations from '~/contributors/stores/mutations';
import * as types from '~/contributors/stores/mutation_types';
+import mutations from '~/contributors/stores/mutations';
+import state from '~/contributors/stores/state';
describe('Contributors mutations', () => {
let stateCopy;
diff --git a/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js b/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
index 90c0e2d7827..0c6095e601f 100644
--- a/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
+++ b/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
@@ -1,7 +1,7 @@
+import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
-import { GlIcon } from '@gitlab/ui';
import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
diff --git a/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js b/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
index e0913fe2e88..95810e882a1 100644
--- a/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import CreateEksCluster from '~/create_cluster/eks_cluster/components/create_eks_cluster.vue';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
index a4835f8c1c1..53a6f12c381 100644
--- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
@@ -1,7 +1,7 @@
+import { GlFormCheckbox } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
import Vue from 'vue';
-import { GlFormCheckbox } from '@gitlab/ui';
+import Vuex from 'vuex';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import eksClusterFormState from '~/create_cluster/eks_cluster/store/state';
diff --git a/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
index d2d6db31d1b..d866ffd4efb 100644
--- a/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import ServiceCredentialsForm from '~/create_cluster/eks_cluster/components/service_credentials_form.vue';
import eksClusterState from '~/create_cluster/eks_cluster/store/state';
diff --git a/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js b/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js
index 2853d18e2cb..7b93b6d0a09 100644
--- a/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js
@@ -1,5 +1,5 @@
-import AWS from 'aws-sdk/global';
import EC2 from 'aws-sdk/clients/ec2';
+import AWS from 'aws-sdk/global';
import {
setAWSConfig,
fetchRoles,
diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
index 35348d3a03b..f10cf4b4140 100644
--- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
@@ -1,7 +1,7 @@
-import testAction from 'helpers/vuex_action_helper';
-import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import MockAdapter from 'axios-mock-adapter';
-import createState from '~/create_cluster/eks_cluster/store/state';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import testAction from 'helpers/vuex_action_helper';
+import { DEFAULT_REGION } from '~/create_cluster/eks_cluster/constants';
import * as actions from '~/create_cluster/eks_cluster/store/actions';
import {
SET_CLUSTER_NAME,
@@ -23,9 +23,9 @@ import {
REQUEST_CREATE_CLUSTER,
CREATE_CLUSTER_ERROR,
} from '~/create_cluster/eks_cluster/store/mutation_types';
-import { DEFAULT_REGION } from '~/create_cluster/eks_cluster/constants';
-import axios from '~/lib/utils/axios_utils';
+import createState from '~/create_cluster/eks_cluster/store/state';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
jest.mock('~/flash');
diff --git a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
index 633cea595d1..54d66e79be7 100644
--- a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
@@ -17,8 +17,8 @@ import {
REQUEST_CREATE_CLUSTER,
CREATE_CLUSTER_ERROR,
} from '~/create_cluster/eks_cluster/store/mutation_types';
-import createState from '~/create_cluster/eks_cluster/store/state';
import mutations from '~/create_cluster/eks_cluster/store/mutations';
+import createState from '~/create_cluster/eks_cluster/store/state';
describe('Create EKS cluster store mutations', () => {
let clusterName;
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
index c09eaa63d4d..8f4903dd91b 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
@@ -1,10 +1,10 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { selectedMachineTypeMock, gapiMachineTypesResponseMock } from '../mock_data';
+import GkeMachineTypeDropdown from '~/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue';
import createState from '~/create_cluster/gke_cluster/store/state';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
-import GkeMachineTypeDropdown from '~/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue';
+import { selectedMachineTypeMock, gapiMachineTypesResponseMock } from '../mock_data';
const componentConfig = {
fieldId: 'cluster_provider_gcp_attributes_gcp_machine_type',
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js
index ce24d186511..23a56766037 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import GkeNetworkDropdown from '~/create_cluster/gke_cluster/components/gke_network_dropdown.vue';
+import Vuex from 'vuex';
import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
+import GkeNetworkDropdown from '~/create_cluster/gke_cluster/components/gke_network_dropdown.vue';
import createClusterDropdownState from '~/create_cluster/store/cluster_dropdown/state';
const localVue = createLocalVue();
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
index eb58108bf3c..b191b107609 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
@@ -1,10 +1,10 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import createState from '~/create_cluster/gke_cluster/store/state';
-import { selectedProjectMock, gapiProjectsResponseMock } from '../mock_data';
import GkeProjectIdDropdown from '~/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue';
+import createState from '~/create_cluster/gke_cluster/store/state';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
+import { selectedProjectMock, gapiProjectsResponseMock } from '../mock_data';
const componentConfig = {
docsUrl: 'https://console.cloud.google.com/home/dashboard',
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js
index 9401ba83ef4..014ed6013bd 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import GkeSubmitButton from '~/create_cluster/gke_cluster/components/gke_submit_button.vue';
const localVue = createLocalVue();
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js
index 35e43d5b033..cfa8a678a9b 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import GkeSubnetworkDropdown from '~/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue';
+import Vuex from 'vuex';
import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
+import GkeSubnetworkDropdown from '~/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue';
import createClusterDropdownState from '~/create_cluster/store/cluster_dropdown/state';
const localVue = createLocalVue();
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
index c07e3f81964..4054b768e34 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
@@ -1,13 +1,13 @@
import { shallowMount } from '@vue/test-utils';
import GkeZoneDropdown from '~/create_cluster/gke_cluster/components/gke_zone_dropdown.vue';
-import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
-import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import { createStore } from '~/create_cluster/gke_cluster/store';
import {
SET_PROJECT,
SET_ZONES,
SET_PROJECT_BILLING_STATUS,
} from '~/create_cluster/gke_cluster/store/mutation_types';
+import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
+import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import { selectedZoneMock, selectedProjectMock, gapiZonesResponseMock } from '../mock_data';
const propsData = {
diff --git a/spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js b/spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js
index 8852baafec7..55c502b96bb 100644
--- a/spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js
@@ -1,7 +1,7 @@
import testAction from 'helpers/vuex_action_helper';
-import createState from '~/create_cluster/gke_cluster/store/state';
-import * as types from '~/create_cluster/gke_cluster/store/mutation_types';
import * as actions from '~/create_cluster/gke_cluster/store/actions';
+import * as types from '~/create_cluster/gke_cluster/store/mutation_types';
+import createState from '~/create_cluster/gke_cluster/store/state';
import gapi from '../helpers';
import {
selectedProjectMock,
diff --git a/spec/frontend/create_cluster/gke_cluster/stores/mutations_spec.js b/spec/frontend/create_cluster/gke_cluster/stores/mutations_spec.js
index 2a742b6ed8f..4493d49af43 100644
--- a/spec/frontend/create_cluster/gke_cluster/stores/mutations_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/stores/mutations_spec.js
@@ -1,6 +1,6 @@
-import createState from '~/create_cluster/gke_cluster/store/state';
import * as types from '~/create_cluster/gke_cluster/store/mutation_types';
import mutations from '~/create_cluster/gke_cluster/store/mutations';
+import createState from '~/create_cluster/gke_cluster/store/state';
import {
gapiProjectsResponseMock,
gapiZonesResponseMock,
diff --git a/spec/frontend/create_cluster/init_create_cluster_spec.js b/spec/frontend/create_cluster/init_create_cluster_spec.js
index 1fdcb57492d..42d1ceed864 100644
--- a/spec/frontend/create_cluster/init_create_cluster_spec.js
+++ b/spec/frontend/create_cluster/init_create_cluster_spec.js
@@ -1,6 +1,6 @@
-import initCreateCluster from '~/create_cluster/init_create_cluster';
import initGkeDropdowns from '~/create_cluster/gke_cluster';
import initGkeNamespace from '~/create_cluster/gke_cluster_namespace';
+import initCreateCluster from '~/create_cluster/init_create_cluster';
import PersistentUserCallout from '~/persistent_user_callout';
// This import is loaded dynamically in `init_create_cluster`.
diff --git a/spec/frontend/create_cluster/store/cluster_dropdown/actions_spec.js b/spec/frontend/create_cluster/store/cluster_dropdown/actions_spec.js
index 014b527161f..c0e8b11cf1e 100644
--- a/spec/frontend/create_cluster/store/cluster_dropdown/actions_spec.js
+++ b/spec/frontend/create_cluster/store/cluster_dropdown/actions_spec.js
@@ -1,8 +1,8 @@
import testAction from 'helpers/vuex_action_helper';
-import createState from '~/create_cluster/store/cluster_dropdown/state';
-import * as types from '~/create_cluster/store/cluster_dropdown/mutation_types';
import actionsFactory from '~/create_cluster/store/cluster_dropdown/actions';
+import * as types from '~/create_cluster/store/cluster_dropdown/mutation_types';
+import createState from '~/create_cluster/store/cluster_dropdown/state';
describe('Cluster dropdown Store Actions', () => {
const items = [{ name: 'item 1' }];
diff --git a/spec/frontend/create_cluster/store/cluster_dropdown/mutations_spec.js b/spec/frontend/create_cluster/store/cluster_dropdown/mutations_spec.js
index 4b700e31675..197fcfc2600 100644
--- a/spec/frontend/create_cluster/store/cluster_dropdown/mutations_spec.js
+++ b/spec/frontend/create_cluster/store/cluster_dropdown/mutations_spec.js
@@ -3,8 +3,8 @@ import {
RECEIVE_ITEMS_SUCCESS,
RECEIVE_ITEMS_ERROR,
} from '~/create_cluster/store/cluster_dropdown/mutation_types';
-import createState from '~/create_cluster/store/cluster_dropdown/state';
import mutations from '~/create_cluster/store/cluster_dropdown/mutations';
+import createState from '~/create_cluster/store/cluster_dropdown/state';
describe('Cluster dropdown store mutations', () => {
let state;
diff --git a/spec/frontend/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js
index 698725b769d..08c05c6ec38 100644
--- a/spec/frontend/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/create_merge_request_dropdown_spec.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
-import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
import confidentialState from '~/confidential_merge_request/state';
+import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
+import axios from '~/lib/utils/axios_utils';
describe('CreateMergeRequestDropdown', () => {
let axiosMock;
diff --git a/spec/frontend/cycle_analytics/limit_warning_component_spec.js b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
index edde3725dd6..3dac7438909 100644
--- a/spec/frontend/cycle_analytics/limit_warning_component_spec.js
+++ b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
@@ -1,7 +1,7 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
-import Translate from '~/vue_shared/translate';
+import Vue from 'vue';
import LimitWarningComponent from '~/cycle_analytics/components/limit_warning_component.vue';
+import Translate from '~/vue_shared/translate';
Vue.use(Translate);
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 650825d1cb7..d8ce184940a 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
@@ -1,9 +1,9 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlButton, GlModal } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import DeployFreezeModal from '~/deploy_freeze/components/deploy_freeze_modal.vue';
-import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue';
import createStore from '~/deploy_freeze/store';
+import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue';
import { freezePeriodsFixture, timezoneDataFixture } from '../helpers';
const localVue = createLocalVue();
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 c29a0c0ca73..392652292cf 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import DeployFreezeModal from '~/deploy_freeze/components/deploy_freeze_modal.vue';
import DeployFreezeSettings from '~/deploy_freeze/components/deploy_freeze_settings.vue';
import DeployFreezeTable from '~/deploy_freeze/components/deploy_freeze_table.vue';
-import DeployFreezeModal from '~/deploy_freeze/components/deploy_freeze_modal.vue';
import createStore from '~/deploy_freeze/store';
import { timezoneDataFixture } from '../helpers';
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 3b8e8f8485e..e4ee1b9ad26 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
+import Vuex from 'vuex';
import DeployFreezeTable from '~/deploy_freeze/components/deploy_freeze_table.vue';
import createStore from '~/deploy_freeze/store';
import { freezePeriodsFixture, timezoneDataFixture } from '../helpers';
diff --git a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
index 1b03cc03d02..4dd5c29a917 100644
--- a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
+++ b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDropdownItem, GlDropdown } from '@gitlab/ui';
-import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import createStore from '~/deploy_freeze/store';
+import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue';
import { findTzByName, formatTz, timezoneDataFixture } from '../helpers';
const localVue = createLocalVue();
diff --git a/spec/frontend/deploy_freeze/store/actions_spec.js b/spec/frontend/deploy_freeze/store/actions_spec.js
index 3c9d25c4f5c..f4d9802e39a 100644
--- a/spec/frontend/deploy_freeze/store/actions_spec.js
+++ b/spec/frontend/deploy_freeze/store/actions_spec.js
@@ -1,11 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import getInitialState from '~/deploy_freeze/store/state';
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 { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { freezePeriodsFixture, timezoneDataFixture } from '../helpers';
jest.mock('~/api.js');
diff --git a/spec/frontend/deploy_freeze/store/mutations_spec.js b/spec/frontend/deploy_freeze/store/mutations_spec.js
index 7cb208f16b2..54cbdfcb64c 100644
--- a/spec/frontend/deploy_freeze/store/mutations_spec.js
+++ b/spec/frontend/deploy_freeze/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/deploy_freeze/store/state';
-import mutations from '~/deploy_freeze/store/mutations';
import * as types from '~/deploy_freeze/store/mutation_types';
+import mutations from '~/deploy_freeze/store/mutations';
+import state from '~/deploy_freeze/store/state';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { findTzByName, formatTz, freezePeriodsFixture, timezoneDataFixture } from '../helpers';
diff --git a/spec/frontend/deploy_keys/components/action_btn_spec.js b/spec/frontend/deploy_keys/components/action_btn_spec.js
index b8211b02464..21281ff15b1 100644
--- a/spec/frontend/deploy_keys/components/action_btn_spec.js
+++ b/spec/frontend/deploy_keys/components/action_btn_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
-import eventHub from '~/deploy_keys/eventhub';
+import { shallowMount } from '@vue/test-utils';
import actionBtn from '~/deploy_keys/components/action_btn.vue';
+import eventHub from '~/deploy_keys/eventhub';
describe('Deploy keys action btn', () => {
const data = getJSONFixture('deploy_keys/keys.json');
diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js
index 479320f92f2..b48e0424580 100644
--- a/spec/frontend/deploy_keys/components/app_spec.js
+++ b/spec/frontend/deploy_keys/components/app_spec.js
@@ -1,10 +1,10 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
-import eventHub from '~/deploy_keys/eventhub';
+import { TEST_HOST } from 'spec/test_constants';
import deployKeysApp from '~/deploy_keys/components/app.vue';
+import eventHub from '~/deploy_keys/eventhub';
+import axios from '~/lib/utils/axios_utils';
const TEST_ENDPOINT = `${TEST_HOST}/dummy/`;
diff --git a/spec/frontend/deploy_keys/components/key_spec.js b/spec/frontend/deploy_keys/components/key_spec.js
index fcb4e31dec8..5420f9a01f9 100644
--- a/spec/frontend/deploy_keys/components/key_spec.js
+++ b/spec/frontend/deploy_keys/components/key_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
-import DeployKeysStore from '~/deploy_keys/store';
import key from '~/deploy_keys/components/key.vue';
+import DeployKeysStore from '~/deploy_keys/store';
import { getTimeago } from '~/lib/utils/datetime_utility';
describe('Deploy keys key', () => {
@@ -76,7 +76,7 @@ describe('Deploy keys key', () => {
createComponent({ deployKey: { ...deployKey, deploy_keys_projects: deployKeysProjects } });
expect(wrapper.find('.deploy-project-label').attributes('title')).toBe(
- 'Write access allowed',
+ 'Grant write permissions to this key',
);
});
diff --git a/spec/frontend/deploy_keys/components/keys_panel_spec.js b/spec/frontend/deploy_keys/components/keys_panel_spec.js
index 34b592017e9..d6419356166 100644
--- a/spec/frontend/deploy_keys/components/keys_panel_spec.js
+++ b/spec/frontend/deploy_keys/components/keys_panel_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
-import DeployKeysStore from '~/deploy_keys/store';
import deployKeysPanel from '~/deploy_keys/components/keys_panel.vue';
+import DeployKeysStore from '~/deploy_keys/store';
describe('Deploy keys panel', () => {
const data = getJSONFixture('deploy_keys/keys.json');
diff --git a/spec/frontend/design_management/components/delete_button_spec.js b/spec/frontend/design_management/components/delete_button_spec.js
index 961f5bdd2ae..8f7d8e0b214 100644
--- a/spec/frontend/design_management/components/delete_button_spec.js
+++ b/spec/frontend/design_management/components/delete_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import BatchDeleteButton from '~/design_management/components/delete_button.vue';
describe('Batch delete button component', () => {
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 77fc70e08d1..92e188f4bcc 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,14 +1,14 @@
-import { mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
-import notes from '../../mock_data/notes';
+import { mount } from '@vue/test-utils';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
+import 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 ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
import mockDiscussion from '../../mock_data/discussion';
+import notes from '../../mock_data/notes';
const defaultMockDiscussion = {
id: '0',
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 043091e3dc2..1cd556eabb4 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,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
const scrollIntoViewMock = jest.fn();
const note = {
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 7eda294d2d3..f87228663b6 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
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { shallowMount } from '@vue/test-utils';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import notes from '../../mock_data/notes';
describe('Toggle replies widget component', () => {
diff --git a/spec/frontend/design_management/components/design_overlay_spec.js b/spec/frontend/design_management/components/design_overlay_spec.js
index a026cc39c84..d3119be7159 100644
--- a/spec/frontend/design_management/components/design_overlay_spec.js
+++ b/spec/frontend/design_management/components/design_overlay_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
import DesignOverlay from '~/design_management/components/design_overlay.vue';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management/constants';
import updateActiveDiscussion from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
import notes from '../mock_data/notes';
-import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management/constants';
const mutate = jest.fn(() => Promise.resolve());
diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js
index 31fd154dc4b..edf8b965153 100644
--- a/spec/frontend/design_management/components/design_presentation_spec.js
+++ b/spec/frontend/design_management/components/design_presentation_spec.js
@@ -1,7 +1,7 @@
-import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
-import DesignPresentation from '~/design_management/components/design_presentation.vue';
+import { nextTick } from 'vue';
import DesignOverlay from '~/design_management/components/design_overlay.vue';
+import DesignPresentation from '~/design_management/components/design_presentation.vue';
const mockOverlayData = {
overlayDimensions: {
diff --git a/spec/frontend/design_management/components/design_scaler_spec.js b/spec/frontend/design_management/components/design_scaler_spec.js
index 40f53e8d0bf..8a123b2d1e5 100644
--- a/spec/frontend/design_management/components/design_scaler_spec.js
+++ b/spec/frontend/design_management/components/design_scaler_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DesignScaler from '~/design_management/components/design_scaler.vue';
describe('Design management design scaler component', () => {
diff --git a/spec/frontend/design_management/components/design_sidebar_spec.js b/spec/frontend/design_management/components/design_sidebar_spec.js
index 60266883fcd..8eb993ec7b5 100644
--- a/spec/frontend/design_management/components/design_sidebar_spec.js
+++ b/spec/frontend/design_management/components/design_sidebar_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount } from '@vue/test-utils';
import { GlCollapse, GlPopover } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
+import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
+import DesignTodoButton from '~/design_management/components/design_todo_button.vue';
+import updateActiveDiscussionMutation from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
import Participants from '~/sidebar/components/participants/participants.vue';
-import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import design from '../mock_data/design';
-import updateActiveDiscussionMutation from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
-import DesignTodoButton from '~/design_management/components/design_todo_button.vue';
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
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 9ebc6ca26a2..20686d0ae6c 100644
--- a/spec/frontend/design_management/components/design_todo_button_spec.js
+++ b/spec/frontend/design_management/components/design_todo_button_spec.js
@@ -1,8 +1,8 @@
import { shallowMount, mount } from '@vue/test-utils';
-import TodoButton from '~/vue_shared/components/todo_button.vue';
import DesignTodoButton from '~/design_management/components/design_todo_button.vue';
import createDesignTodoMutation from '~/design_management/graphql/mutations/create_design_todo.mutation.graphql';
import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
+import TodoButton from '~/vue_shared/components/todo_button.vue';
import mockDesign from '../mock_data/design';
const mockDesignWithPendingTodos = {
@@ -111,7 +111,7 @@ describe('Design management design todo button', () => {
});
it('renders correct button text', () => {
- expect(wrapper.text()).toBe('Add a To Do');
+ expect(wrapper.text()).toBe('Add a to do');
});
describe('when clicked', () => {
diff --git a/spec/frontend/design_management/components/image_spec.js b/spec/frontend/design_management/components/image_spec.js
index 52d60b04a8a..765d902f9a6 100644
--- a/spec/frontend/design_management/components/image_spec.js
+++ b/spec/frontend/design_management/components/image_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DesignImage from '~/design_management/components/image.vue';
describe('Design management large image component', () => {
diff --git a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap
index 36a2ffd19c3..8fe3e92360a 100644
--- a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap
+++ b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap
@@ -26,9 +26,10 @@ exports[`Design management list item component with notes renders item with mult
<img
alt="test"
- class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img"
+ class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
data-qa-filename="test"
data-qa-selector="design_image"
+ data-testid="design-img-1"
src=""
/>
</gl-intersection-observer-stub>
@@ -43,6 +44,8 @@ exports[`Design management list item component with notes renders item with mult
<span
class="gl-font-weight-bold str-truncated-100"
data-qa-selector="design_file_name"
+ data-testid="design-img-filename-1"
+ title="test"
>
test
</span>
@@ -100,9 +103,10 @@ exports[`Design management list item component with notes renders item with sing
<img
alt="test"
- class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img"
+ class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
data-qa-filename="test"
data-qa-selector="design_image"
+ data-testid="design-img-1"
src=""
/>
</gl-intersection-observer-stub>
@@ -117,6 +121,8 @@ exports[`Design management list item component with notes renders item with sing
<span
class="gl-font-weight-bold str-truncated-100"
data-qa-selector="design_file_name"
+ data-testid="design-img-filename-1"
+ title="test"
>
test
</span>
diff --git a/spec/frontend/design_management/components/list/item_spec.js b/spec/frontend/design_management/components/list/item_spec.js
index 55c6ecbc26b..caf0f8bb5bc 100644
--- a/spec/frontend/design_management/components/list/item_spec.js
+++ b/spec/frontend/design_management/components/list/item_spec.js
@@ -1,6 +1,7 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueRouter from 'vue-router';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Item from '~/design_management/components/list/item.vue';
const localVue = createLocalVue();
@@ -17,8 +18,11 @@ const DESIGN_VERSION_EVENT = {
describe('Design management list item component', () => {
let wrapper;
+ const imgId = 1;
+ const imgFilename = 'test';
- const findDesignEvent = () => wrapper.find('[data-testid="designEvent"]');
+ const findDesignEvent = () => wrapper.findByTestId('design-event');
+ const findImgFilename = (id = imgId) => wrapper.findByTestId(`design-img-filename-${id}`);
const findEventIcon = () => findDesignEvent().find(GlIcon);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
@@ -28,25 +32,27 @@ describe('Design management list item component', () => {
isUploading = false,
imageLoading = false,
} = {}) {
- wrapper = shallowMount(Item, {
- localVue,
- router,
- propsData: {
- id: 1,
- filename: 'test',
- image: 'http://via.placeholder.com/300',
- isUploading,
- event,
- notesCount,
- updatedAt: '01-01-2019',
- },
- data() {
- return {
- imageLoading,
- };
- },
- stubs: ['router-link'],
- });
+ wrapper = extendedWrapper(
+ shallowMount(Item, {
+ localVue,
+ router,
+ propsData: {
+ id: imgId,
+ filename: imgFilename,
+ image: 'http://via.placeholder.com/300',
+ isUploading,
+ event,
+ notesCount,
+ updatedAt: '01-01-2019',
+ },
+ data() {
+ return {
+ imageLoading,
+ };
+ },
+ stubs: ['router-link'],
+ }),
+ );
}
afterEach(() => {
@@ -75,6 +81,10 @@ describe('Design management list item component', () => {
return wrapper.vm.$nextTick();
});
+ it('renders a tooltip', () => {
+ expect(findImgFilename().attributes('title')).toEqual(imgFilename);
+ });
+
describe('before image is loaded', () => {
it('renders loading spinner', () => {
expect(wrapper.find(GlLoadingIcon)).toExist();
diff --git a/spec/frontend/design_management/components/toolbar/index_spec.js b/spec/frontend/design_management/components/toolbar/index_spec.js
index 6ac088a2c53..44c865d976d 100644
--- a/spec/frontend/design_management/components/toolbar/index_spec.js
+++ b/spec/frontend/design_management/components/toolbar/index_spec.js
@@ -1,8 +1,8 @@
+import { GlButton } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueRouter from 'vue-router';
-import { GlButton } from '@gitlab/ui';
-import Toolbar from '~/design_management/components/toolbar/index.vue';
import DeleteButton from '~/design_management/components/delete_button.vue';
+import Toolbar from '~/design_management/components/toolbar/index.vue';
import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants';
const localVue = createLocalVue();
diff --git a/spec/frontend/design_management/components/upload/button_spec.js b/spec/frontend/design_management/components/upload/button_spec.js
index ea738496ad6..d123db43ce6 100644
--- a/spec/frontend/design_management/components/upload/button_spec.js
+++ b/spec/frontend/design_management/components/upload/button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UploadButton from '~/design_management/components/upload/button.vue';
describe('Design management upload button component', () => {
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 d841aabf2f3..1b01a363688 100644
--- a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
+++ b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue';
import mockAllVersions from './mock_data/all_versions';
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index 9c11af28cf0..11c88c3d0f5 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -1,32 +1,32 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import VueRouter from 'vue-router';
import { GlAlert } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
+import VueRouter from 'vue-router';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import createFlash from '~/flash';
import Api from '~/api';
-import DesignIndex from '~/design_management/pages/design/index.vue';
-import DesignSidebar from '~/design_management/components/design_sidebar.vue';
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 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';
+import * as utils from '~/design_management/utils/design_management_utils';
import {
DESIGN_NOT_FOUND_ERROR,
DESIGN_VERSION_NOT_EXIST_ERROR,
} from '~/design_management/utils/error_messages';
-import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
-import createRouter from '~/design_management/router';
-import * as utils from '~/design_management/utils/design_management_utils';
-import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management/constants';
-import design from '../../mock_data/design';
-import mockResponseWithDesigns from '../../mock_data/designs';
-import mockResponseNoDesigns from '../../mock_data/no_designs';
-import mockAllVersions from '../../mock_data/all_versions';
import {
DESIGN_TRACKING_PAGE_NAME,
DESIGN_SNOWPLOW_EVENT_TYPES,
DESIGN_USAGE_PING_EVENT_TYPES,
} from '~/design_management/utils/tracking';
+import createFlash from '~/flash';
+import mockAllVersions from '../../mock_data/all_versions';
+import design from '../../mock_data/design';
+import mockResponseWithDesigns from '../../mock_data/designs';
+import mockResponseNoDesigns from '../../mock_data/no_designs';
jest.mock('~/flash');
jest.mock('~/api.js');
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index 7d28d6f6d11..4f162ca8e7f 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -1,27 +1,32 @@
-import { nextTick } from 'vue';
+import { GlEmptyState } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import VueApollo, { ApolloMutation } from 'vue-apollo';
-import VueDraggable from 'vuedraggable';
import VueRouter from 'vue-router';
-import { GlEmptyState } from '@gitlab/ui';
+import VueDraggable from 'vuedraggable';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
-import Index from '~/design_management/pages/index.vue';
-import uploadDesignMutation from '~/design_management/graphql/mutations/upload_design.mutation.graphql';
-import DesignDestroyer from '~/design_management/components/design_destroyer.vue';
-import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
+import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
import DeleteButton from '~/design_management/components/delete_button.vue';
+import DesignDestroyer from '~/design_management/components/design_destroyer.vue';
import Design from '~/design_management/components/list/item.vue';
+import moveDesignMutation from '~/design_management/graphql/mutations/move_design.mutation.graphql';
+import uploadDesignMutation from '~/design_management/graphql/mutations/upload_design.mutation.graphql';
+import Index from '~/design_management/pages/index.vue';
+import createRouter from '~/design_management/router';
import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants';
+import * as utils from '~/design_management/utils/design_management_utils';
import {
EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
} from '~/design_management/utils/error_messages';
+import {
+ DESIGN_TRACKING_PAGE_NAME,
+ DESIGN_SNOWPLOW_EVENT_TYPES,
+} from '~/design_management/utils/tracking';
import createFlash from '~/flash';
-import createRouter from '~/design_management/router';
-import * as utils from '~/design_management/utils/design_management_utils';
+import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
import {
designListQueryResponse,
designUploadMutationCreatedResponse,
@@ -31,11 +36,6 @@ import {
reorderedDesigns,
moveDesignMutationResponseWithErrors,
} from '../mock_data/apollo_mock';
-import moveDesignMutation from '~/design_management/graphql/mutations/move_design.mutation.graphql';
-import {
- DESIGN_TRACKING_PAGE_NAME,
- DESIGN_SNOWPLOW_EVENT_TYPES,
-} from '~/design_management/utils/tracking';
jest.mock('~/flash.js');
const mockPageEl = {
diff --git a/spec/frontend/design_management/router_spec.js b/spec/frontend/design_management/router_spec.js
index 0b4e68eea78..ac5e6895408 100644
--- a/spec/frontend/design_management/router_spec.js
+++ b/spec/frontend/design_management/router_spec.js
@@ -2,8 +2,8 @@ import { mount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueRouter from 'vue-router';
import App from '~/design_management/components/app.vue';
-import Designs from '~/design_management/pages/index.vue';
import DesignDetail from '~/design_management/pages/design/index.vue';
+import Designs from '~/design_management/pages/index.vue';
import createRouter from '~/design_management/router';
import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
import '~/commons/bootstrap';
diff --git a/spec/frontend/design_management/utils/cache_update_spec.js b/spec/frontend/design_management/utils/cache_update_spec.js
index 2fb08c3ef05..7327cf00abd 100644
--- a/spec/frontend/design_management/utils/cache_update_spec.js
+++ b/spec/frontend/design_management/utils/cache_update_spec.js
@@ -10,8 +10,8 @@ import {
ADD_IMAGE_DIFF_NOTE_ERROR,
UPDATE_IMAGE_DIFF_NOTE_ERROR,
} from '~/design_management/utils/error_messages';
-import design from '../mock_data/design';
import createFlash from '~/flash';
+import design from '../mock_data/design';
jest.mock('~/flash.js');
diff --git a/spec/frontend/design_management/utils/design_management_utils_spec.js b/spec/frontend/design_management/utils/design_management_utils_spec.js
index 368448ead10..5b7f99e9d96 100644
--- a/spec/frontend/design_management/utils/design_management_utils_spec.js
+++ b/spec/frontend/design_management/utils/design_management_utils_spec.js
@@ -8,9 +8,9 @@ import {
extractDesign,
extractDesignNoteId,
} from '~/design_management/utils/design_management_utils';
-import mockResponseNoDesigns from '../mock_data/no_designs';
-import mockResponseWithDesigns from '../mock_data/designs';
import mockDesign from '../mock_data/design';
+import mockResponseWithDesigns from '../mock_data/designs';
+import mockResponseNoDesigns from '../mock_data/no_designs';
jest.mock('lodash/uniqueId', () => () => 1);
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 7fbeb33dd93..d2b5338a0cc 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -1,26 +1,26 @@
-import Vue, { nextTick } from 'vue';
-import Vuex from 'vuex';
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlPagination } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
import Mousetrap from 'mousetrap';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
-import NoChanges from '~/diffs/components/no_changes.vue';
-import DiffFile from '~/diffs/components/diff_file.vue';
-import CompareVersions from '~/diffs/components/compare_versions.vue';
-import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue';
import CommitWidget from '~/diffs/components/commit_widget.vue';
+import CompareVersions from '~/diffs/components/compare_versions.vue';
+import DiffFile from '~/diffs/components/diff_file.vue';
+import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
+import NoChanges from '~/diffs/components/no_changes.vue';
import TreeList from '~/diffs/components/tree_list.vue';
-import createDiffsStore from '../create_diffs_store';
-import axios from '~/lib/utils/axios_utils';
-import * as urlUtils from '~/lib/utils/url_utility';
-import diffsMockData from '../mock_data/merge_request_diffs';
import { EVT_VIEW_FILE_BY_FILE } from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
+import axios from '~/lib/utils/axios_utils';
+import * as urlUtils from '~/lib/utils/url_utility';
+import createDiffsStore from '../create_diffs_store';
+import diffsMockData from '../mock_data/merge_request_diffs';
const mergeRequestDiff = { version_index: 1 };
const TEST_ENDPOINT = `${TEST_HOST}/diff/endpoint`;
diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
index 75e76d88b6b..77c2e19cb68 100644
--- a/spec/frontend/diffs/components/collapsed_files_warning_spec.js
+++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
@@ -1,9 +1,9 @@
-import Vuex from 'vuex';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
-import createStore from '~/diffs/store/modules';
+import Vuex from 'vuex';
import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue';
import { CENTERED_LIMITED_CONTAINER_CLASSES, EVT_EXPAND_ALL_FILES } from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
+import createStore from '~/diffs/store/modules';
const propsData = {
limited: true,
diff --git a/spec/frontend/diffs/components/commit_item_spec.js b/spec/frontend/diffs/components/commit_item_spec.js
index f588f65dafd..8cb4fd20063 100644
--- a/spec/frontend/diffs/components/commit_item_spec.js
+++ b/spec/frontend/diffs/components/commit_item_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
-import { getTimeago } from '~/lib/utils/datetime_utility';
import Component from '~/diffs/components/commit_item.vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import getDiffWithCommit from '../mock_data/diff_with_commit';
diff --git a/spec/frontend/diffs/components/commit_widget_spec.js b/spec/frontend/diffs/components/commit_widget_spec.js
index 54e7596b726..fbff473e4df 100644
--- a/spec/frontend/diffs/components/commit_widget_spec.js
+++ b/spec/frontend/diffs/components/commit_widget_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import CommitWidget from '~/diffs/components/commit_widget.vue';
import CommitItem from '~/diffs/components/commit_item.vue';
+import CommitWidget from '~/diffs/components/commit_widget.vue';
describe('diffs/components/commit_widget', () => {
let wrapper;
diff --git a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
index d99933a1ee9..98f88226742 100644
--- a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
+++ b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import CompareDropdownLayout from '~/diffs/components/compare_dropdown_layout.vue';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
const TEST_COMMIT_TEXT = '1 commit';
const TEST_CREATED_AT = '2018-10-23T11:49:16.611Z';
@@ -69,7 +69,7 @@ describe('CompareDropdownLayout', () => {
expect(findListItemsData()).toEqual([
{
href: 'version/1',
- text: 'version 1 (base) abcdef1 1 commit 2 years ago',
+ text: 'version 1 (base) abcdef1 1 commit 1 year ago',
createdAt: TEST_CREATED_AT,
isActive: true,
},
diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js
index 949cc855200..c93a3771ec0 100644
--- a/spec/frontend/diffs/components/compare_versions_spec.js
+++ b/spec/frontend/diffs/components/compare_versions_spec.js
@@ -1,10 +1,10 @@
-import { trimText } from 'helpers/text_helper';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
+import { trimText } from 'helpers/text_helper';
import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
import { createStore } from '~/mr_notes/stores';
-import diffsMockData from '../mock_data/merge_request_diffs';
import getDiffWithCommit from '../mock_data/diff_with_commit';
+import diffsMockData from '../mock_data/merge_request_diffs';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/diffs/components/diff_comment_cell_spec.js b/spec/frontend/diffs/components/diff_comment_cell_spec.js
index d6b68fc52d7..b636a178593 100644
--- a/spec/frontend/diffs/components/diff_comment_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_comment_cell_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import DiffCommentCell from '~/diffs/components/diff_comment_cell.vue';
-import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
import DiffDiscussionReply from '~/diffs/components/diff_discussion_reply.vue';
+import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
describe('DiffCommentCell', () => {
const createWrapper = (props = {}) => {
diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js
index c1cf4793c88..db4d69f0176 100644
--- a/spec/frontend/diffs/components/diff_content_spec.js
+++ b/spec/frontend/diffs/components/diff_content_spec.js
@@ -1,17 +1,17 @@
+import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
import DiffContentComponent from '~/diffs/components/diff_content.vue';
+import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
+import DiffView from '~/diffs/components/diff_view.vue';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
-import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
-import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
-import NoteForm from '~/notes/components/note_form.vue';
-import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
-import diffFileMockData from '../mock_data/diff_file';
import { diffViewerModes } from '~/ide/constants';
-import DiffView from '~/diffs/components/diff_view.vue';
+import NoteForm from '~/notes/components/note_form.vue';
+import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
+import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
+import diffFileMockData from '../mock_data/diff_file';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/diffs/components/diff_discussions_spec.js b/spec/frontend/diffs/components/diff_discussions_spec.js
index 5c390054247..bd6f4cd2545 100644
--- a/spec/frontend/diffs/components/diff_discussions_spec.js
+++ b/spec/frontend/diffs/components/diff_discussions_spec.js
@@ -1,10 +1,10 @@
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
-import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
-import DiscussionNotes from '~/notes/components/discussion_notes.vue';
import { createStore } from '~/mr_notes/stores';
+import DiscussionNotes from '~/notes/components/discussion_notes.vue';
+import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import '~/behaviors/markdown/render_gfm';
import discussionsMockData from '../mock_data/diff_discussions';
diff --git a/spec/frontend/diffs/components/diff_expansion_cell_spec.js b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
index 62e85b31f76..f53f10d955d 100644
--- a/spec/frontend/diffs/components/diff_expansion_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
@@ -1,10 +1,10 @@
-import { cloneDeep } from 'lodash';
-import { mount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
-import { createStore } from '~/mr_notes/stores';
+import { mount } from '@vue/test-utils';
+import { cloneDeep } from 'lodash';
import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
-import { getPreviousLineIndex } from '~/diffs/store/utils';
import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+import { getPreviousLineIndex } from '~/diffs/store/utils';
+import { createStore } from '~/mr_notes/stores';
import diffFileMockData from '../mock_data/diff_file';
const EXPAND_UP_CLASS = '.js-unfold';
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index e9a63e861ed..b16ef8fe6b0 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -1,22 +1,29 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
import { cloneDeep } from 'lodash';
+import Vuex from 'vuex';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
+import { DIFF_FILE_AUTOMATIC_COLLAPSE, DIFF_FILE_MANUAL_COLLAPSE } from '~/diffs/constants';
+import { reviewFile } from '~/diffs/store/actions';
+import { SET_MR_FILE_REVIEWS } from '~/diffs/store/mutation_types';
+import { diffViewerModes } from '~/ide/constants';
+import { scrollToElement } from '~/lib/utils/common_utils';
+import { truncateSha } from '~/lib/utils/text_utility';
+import { __, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+
+import testAction from '../../__helpers__/vuex_action_helper';
import diffDiscussionsMockData from '../mock_data/diff_discussions';
-import { truncateSha } from '~/lib/utils/text_utility';
-import { diffViewerModes } from '~/ide/constants';
-import { __, sprintf } from '~/locale';
-import { scrollToElement } from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/common_utils');
const diffFile = Object.freeze(
Object.assign(diffDiscussionsMockData.diff_file, {
+ id: '123',
+ file_identifier_hash: 'abc',
edit_path: 'link:/to/edit/path',
blob: {
id: '848ed9407c6730ff16edb3dd24485a0eea24292a',
@@ -52,6 +59,8 @@ describe('DiffFileHeader component', () => {
toggleFileDiscussionWrappers: jest.fn(),
toggleFullDiff: jest.fn(),
toggleActiveFileByHash: jest.fn(),
+ setFileCollapsedByUser: jest.fn(),
+ reviewFile: jest.fn(),
},
},
},
@@ -79,10 +88,11 @@ describe('DiffFileHeader component', () => {
const findViewFileButton = () => wrapper.find({ ref: 'viewButton' });
const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' });
const findEditButton = () => wrapper.find({ ref: 'editButton' });
+ const findReviewFileCheckbox = () => wrapper.find("[data-testid='fileReviewCheckbox']");
- const createComponent = (props) => {
+ const createComponent = ({ props, options = {} } = {}) => {
mockStoreConfig = cloneDeep(defaultMockStoreConfig);
- const store = new Vuex.Store(mockStoreConfig);
+ const store = new Vuex.Store({ ...mockStoreConfig, ...(options.store || {}) });
wrapper = shallowMount(DiffFileHeader, {
propsData: {
@@ -91,6 +101,7 @@ describe('DiffFileHeader component', () => {
viewDiffsFileByFile: false,
...props,
},
+ ...options,
localVue,
store,
});
@@ -101,7 +112,7 @@ describe('DiffFileHeader component', () => {
${'visible'} | ${true}
${'hidden'} | ${false}
`('collapse toggle is $visibility if collapsible is $collapsible', ({ collapsible }) => {
- createComponent({ collapsible });
+ createComponent({ props: { collapsible } });
expect(findCollapseIcon().exists()).toBe(collapsible);
});
@@ -110,7 +121,7 @@ describe('DiffFileHeader component', () => {
${true} | ${'chevron-down'}
${false} | ${'chevron-right'}
`('collapse icon is $icon if expanded is $expanded', ({ icon, expanded }) => {
- createComponent({ expanded, collapsible: true });
+ createComponent({ props: { expanded, collapsible: true } });
expect(findCollapseIcon().props('name')).toBe(icon);
});
@@ -124,7 +135,7 @@ describe('DiffFileHeader component', () => {
});
it('when collapseIcon is clicked emits toggleFile', () => {
- createComponent({ collapsible: true });
+ createComponent({ props: { collapsible: true } });
findCollapseIcon().vm.$emit('click', new Event('click'));
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().toggleFile).toBeDefined();
@@ -132,7 +143,7 @@ describe('DiffFileHeader component', () => {
});
it('when other element in header is clicked does not emits toggleFile', () => {
- createComponent({ collapsible: true });
+ createComponent({ props: { collapsible: true } });
findTitleLink().trigger('click');
return wrapper.vm.$nextTick().then(() => {
@@ -171,10 +182,12 @@ describe('DiffFileHeader component', () => {
it('prefers submodule_tree_url over submodule_link for href', () => {
const submoduleTreeUrl = 'some://tree/url';
createComponent({
- discussionLink: 'discussionLink',
- diffFile: {
- ...submoduleDiffFile,
- submodule_tree_url: 'some://tree/url',
+ props: {
+ discussionLink: 'discussionLink',
+ diffFile: {
+ ...submoduleDiffFile,
+ submodule_tree_url: 'some://tree/url',
+ },
},
});
@@ -184,8 +197,10 @@ describe('DiffFileHeader component', () => {
it('uses submodule_link for href if submodule_tree_url does not exists', () => {
const submoduleLink = 'link://to/submodule';
createComponent({
- discussionLink: 'discussionLink',
- diffFile: submoduleDiffFile,
+ props: {
+ discussionLink: 'discussionLink',
+ diffFile: submoduleDiffFile,
+ },
});
expect(findTitleLink().attributes('href')).toBe(submoduleLink);
@@ -193,7 +208,9 @@ describe('DiffFileHeader component', () => {
it('uses file_path + SHA as link text', () => {
createComponent({
- diffFile: submoduleDiffFile,
+ props: {
+ diffFile: submoduleDiffFile,
+ },
});
expect(findTitleLink().text()).toContain(
@@ -203,15 +220,19 @@ describe('DiffFileHeader component', () => {
it('does not render file actions', () => {
createComponent({
- diffFile: submoduleDiffFile,
- addMergeRequestButtons: true,
+ props: {
+ diffFile: submoduleDiffFile,
+ addMergeRequestButtons: true,
+ },
});
expect(findFileActions().exists()).toBe(false);
});
it('renders submodule icon', () => {
createComponent({
- diffFile: submoduleDiffFile,
+ props: {
+ diffFile: submoduleDiffFile,
+ },
});
expect(wrapper.find(FileIcon).props('submodule')).toBe(true);
@@ -223,13 +244,15 @@ describe('DiffFileHeader component', () => {
it('for mode_changed file mode displays mode changes', () => {
createComponent({
- diffFile: {
- ...diffFile,
- a_mode: 'old-mode',
- b_mode: 'new-mode',
- viewer: {
- ...diffFile.viewer,
- name: diffViewerModes.mode_changed,
+ props: {
+ diffFile: {
+ ...diffFile,
+ a_mode: 'old-mode',
+ b_mode: 'new-mode',
+ viewer: {
+ ...diffFile.viewer,
+ name: diffViewerModes.mode_changed,
+ },
},
},
});
@@ -240,13 +263,15 @@ describe('DiffFileHeader component', () => {
'for %s file mode does not display mode changes',
(mode) => {
createComponent({
- diffFile: {
- ...diffFile,
- a_mode: 'old-mode',
- b_mode: 'new-mode',
- viewer: {
- ...diffFile.viewer,
- name: diffViewerModes[mode],
+ props: {
+ diffFile: {
+ ...diffFile,
+ a_mode: 'old-mode',
+ b_mode: 'new-mode',
+ viewer: {
+ ...diffFile.viewer,
+ name: diffViewerModes[mode],
+ },
},
},
});
@@ -256,32 +281,38 @@ describe('DiffFileHeader component', () => {
it('displays the LFS label for files stored in LFS', () => {
createComponent({
- diffFile: { ...diffFile, stored_externally: true, external_storage: 'lfs' },
+ props: {
+ diffFile: { ...diffFile, stored_externally: true, external_storage: 'lfs' },
+ },
});
expect(findLfsLabel().exists()).toBe(true);
});
it('does not display the LFS label for files stored in repository', () => {
createComponent({
- diffFile: { ...diffFile, stored_externally: false },
+ props: {
+ diffFile: { ...diffFile, stored_externally: false },
+ },
});
expect(findLfsLabel().exists()).toBe(false);
});
it('does not render view replaced file button if no replaced view path is present', () => {
createComponent({
- diffFile: { ...diffFile, replaced_view_path: null },
+ props: {
+ diffFile: { ...diffFile, replaced_view_path: null },
+ },
});
expect(findReplacedFileButton().exists()).toBe(false);
});
describe('when addMergeRequestButtons is false', () => {
it('does not render file actions', () => {
- createComponent({ addMergeRequestButtons: false });
+ createComponent({ props: { addMergeRequestButtons: false } });
expect(findFileActions().exists()).toBe(false);
});
it('should not render edit button', () => {
- createComponent({ addMergeRequestButtons: false });
+ createComponent({ props: { addMergeRequestButtons: false } });
expect(findEditButton().exists()).toBe(false);
});
});
@@ -290,7 +321,7 @@ describe('DiffFileHeader component', () => {
describe('without discussions', () => {
it('does not render a toggle discussions button', () => {
diffHasDiscussionsResultMock.mockReturnValue(false);
- createComponent({ addMergeRequestButtons: true });
+ createComponent({ props: { addMergeRequestButtons: true } });
expect(findToggleDiscussionsButton().exists()).toBe(false);
});
});
@@ -298,7 +329,7 @@ describe('DiffFileHeader component', () => {
describe('with discussions', () => {
it('dispatches toggleFileDiscussionWrappers when user clicks on toggle discussions button', () => {
diffHasDiscussionsResultMock.mockReturnValue(true);
- createComponent({ addMergeRequestButtons: true });
+ createComponent({ props: { addMergeRequestButtons: true } });
expect(findToggleDiscussionsButton().exists()).toBe(true);
findToggleDiscussionsButton().vm.$emit('click');
expect(
@@ -309,7 +340,9 @@ describe('DiffFileHeader component', () => {
it('should show edit button', () => {
createComponent({
- addMergeRequestButtons: true,
+ props: {
+ addMergeRequestButtons: true,
+ },
});
expect(findEditButton().exists()).toBe(true);
});
@@ -319,25 +352,27 @@ describe('DiffFileHeader component', () => {
const externalUrl = 'link://to/external';
const formattedExternalUrl = 'link://formatted';
createComponent({
- diffFile: {
- ...diffFile,
- external_url: externalUrl,
- formatted_external_url: formattedExternalUrl,
+ props: {
+ diffFile: {
+ ...diffFile,
+ external_url: externalUrl,
+ formatted_external_url: formattedExternalUrl,
+ },
+ addMergeRequestButtons: true,
},
- addMergeRequestButtons: true,
});
expect(findExternalLink().exists()).toBe(true);
});
it('is hidden by default', () => {
- createComponent({ addMergeRequestButtons: true });
+ createComponent({ props: { addMergeRequestButtons: true } });
expect(findExternalLink().exists()).toBe(false);
});
});
describe('without file blob', () => {
beforeEach(() => {
- createComponent({ diffFile: { ...diffFile, blob: false } });
+ createComponent({ props: { diffFile: { ...diffFile, blob: false } } });
});
it('should not render toggle discussions button', () => {
@@ -352,8 +387,10 @@ describe('DiffFileHeader component', () => {
it('should render correct file view button', () => {
const viewPath = 'link://view-path';
createComponent({
- diffFile: { ...diffFile, view_path: viewPath },
- addMergeRequestButtons: true,
+ props: {
+ diffFile: { ...diffFile, view_path: viewPath },
+ addMergeRequestButtons: true,
+ },
});
expect(findViewFileButton().attributes('href')).toBe(viewPath);
expect(findViewFileButton().text()).toEqual(
@@ -367,9 +404,11 @@ describe('DiffFileHeader component', () => {
describe('when diff is fully expanded', () => {
it('is not rendered', () => {
createComponent({
- diffFile: {
- ...diffFile,
- is_fully_expanded: true,
+ props: {
+ diffFile: {
+ ...diffFile,
+ is_fully_expanded: true,
+ },
},
});
expect(findExpandButton().exists()).toBe(false);
@@ -387,17 +426,17 @@ describe('DiffFileHeader component', () => {
};
it('renders expand to full file button if not showing full file already', () => {
- createComponent(fullyNotExpandedFileProps);
+ createComponent({ props: fullyNotExpandedFileProps });
expect(findExpandButton().exists()).toBe(true);
});
it('renders loading icon when loading full file', () => {
- createComponent(fullyNotExpandedFileProps);
+ createComponent({ props: fullyNotExpandedFileProps });
expect(findExpandButton().exists()).toBe(true);
});
it('toggles full diff on click', () => {
- createComponent(fullyNotExpandedFileProps);
+ createComponent({ props: fullyNotExpandedFileProps });
findExpandButton().vm.$emit('click');
expect(mockStoreConfig.modules.diffs.actions.toggleFullDiff).toHaveBeenCalled();
});
@@ -407,7 +446,9 @@ describe('DiffFileHeader component', () => {
it('uses discussionPath for link if it is defined', () => {
const discussionPath = 'link://to/discussion';
createComponent({
- discussionPath,
+ props: {
+ discussionPath,
+ },
});
expect(findTitleLink().attributes('href')).toBe(discussionPath);
});
@@ -436,21 +477,21 @@ describe('DiffFileHeader component', () => {
describe('for new file', () => {
it('displays the path', () => {
- createComponent({ diffFile: { ...diffFile, new_file: true } });
+ createComponent({ props: { diffFile: { ...diffFile, new_file: true } } });
expect(findTitleLink().text()).toBe(diffFile.file_path);
});
});
describe('for deleted file', () => {
it('displays the path', () => {
- createComponent({ diffFile: { ...diffFile, deleted_file: true } });
+ createComponent({ props: { diffFile: { ...diffFile, deleted_file: true } } });
expect(findTitleLink().text()).toBe(
sprintf(__('%{filePath} deleted'), { filePath: diffFile.file_path }, false),
);
});
it('does not show edit button', () => {
- createComponent({ diffFile: { ...diffFile, deleted_file: true } });
+ createComponent({ props: { diffFile: { ...diffFile, deleted_file: true } } });
expect(findEditButton().exists()).toBe(false);
});
});
@@ -458,11 +499,13 @@ describe('DiffFileHeader component', () => {
describe('for renamed file', () => {
it('displays old and new path if the file was renamed', () => {
createComponent({
- diffFile: {
- ...diffFile,
- renamed_file: true,
- old_path_html: 'old',
- new_path_html: 'new',
+ props: {
+ diffFile: {
+ ...diffFile,
+ renamed_file: true,
+ old_path_html: 'old',
+ new_path_html: 'new',
+ },
},
});
expect(findTitleLink().text()).toMatch(/^old.+new/s);
@@ -473,13 +516,132 @@ describe('DiffFileHeader component', () => {
it('renders view replaced file button', () => {
const replacedViewPath = 'some/path';
createComponent({
- diffFile: {
- ...diffFile,
- replaced_view_path: replacedViewPath,
+ props: {
+ diffFile: {
+ ...diffFile,
+ replaced_view_path: replacedViewPath,
+ },
+ addMergeRequestButtons: true,
},
- addMergeRequestButtons: true,
});
expect(findReplacedFileButton().exists()).toBe(true);
});
});
+
+ describe('file reviews', () => {
+ it('calls the action to set the new review', () => {
+ createComponent({
+ props: {
+ diffFile: {
+ ...diffFile,
+ viewer: {
+ ...diffFile.viewer,
+ automaticallyCollapsed: false,
+ manuallyCollapsed: null,
+ },
+ },
+ showLocalFileReviews: true,
+ addMergeRequestButtons: true,
+ },
+ });
+
+ const file = wrapper.vm.diffFile;
+
+ findReviewFileCheckbox().vm.$emit('change', true);
+
+ return testAction(
+ reviewFile,
+ { file, reviewed: true },
+ {},
+ [{ type: SET_MR_FILE_REVIEWS, payload: { [file.file_identifier_hash]: [file.id] } }],
+ [],
+ );
+ });
+
+ it.each`
+ description | newReviewedStatus | collapseType | aCollapse | mCollapse | callAction
+ ${'does nothing'} | ${true} | ${DIFF_FILE_MANUAL_COLLAPSE} | ${false} | ${true} | ${false}
+ ${'does nothing'} | ${false} | ${DIFF_FILE_AUTOMATIC_COLLAPSE} | ${true} | ${null} | ${false}
+ ${'does nothing'} | ${true} | ${'not collapsed'} | ${false} | ${null} | ${false}
+ ${'does nothing'} | ${false} | ${'not collapsed'} | ${false} | ${null} | ${false}
+ ${'collapses the file'} | ${true} | ${DIFF_FILE_AUTOMATIC_COLLAPSE} | ${true} | ${null} | ${true}
+ `(
+ "$description if the new review status is reviewed = $newReviewedStatus and the file's collapse type is collapse = $collapseType",
+ ({ newReviewedStatus, aCollapse, mCollapse, callAction }) => {
+ createComponent({
+ props: {
+ diffFile: {
+ ...diffFile,
+ viewer: {
+ ...diffFile.viewer,
+ automaticallyCollapsed: aCollapse,
+ manuallyCollapsed: mCollapse,
+ },
+ },
+ showLocalFileReviews: true,
+ addMergeRequestButtons: true,
+ },
+ });
+
+ findReviewFileCheckbox().vm.$emit('change', newReviewedStatus);
+
+ if (callAction) {
+ expect(mockStoreConfig.modules.diffs.actions.setFileCollapsedByUser).toHaveBeenCalled();
+ } else {
+ expect(
+ mockStoreConfig.modules.diffs.actions.setFileCollapsedByUser,
+ ).not.toHaveBeenCalled();
+ }
+ },
+ );
+
+ it.each`
+ description | show | visible
+ ${'shows'} | ${true} | ${true}
+ ${'hides'} | ${false} | ${false}
+ `(
+ '$description the file review feature given { showLocalFileReviewsProp: $show }',
+ ({ show, visible }) => {
+ createComponent({
+ props: {
+ showLocalFileReviews: show,
+ addMergeRequestButtons: true,
+ },
+ });
+
+ expect(findReviewFileCheckbox().exists()).toEqual(visible);
+ },
+ );
+
+ it.each`
+ open | status | fires
+ ${true} | ${true} | ${true}
+ ${false} | ${false} | ${true}
+ ${true} | ${false} | ${false}
+ ${false} | ${true} | ${false}
+ `(
+ 'toggles appropriately when { fileExpanded: $open, newReviewStatus: $status }',
+ ({ open, status, fires }) => {
+ createComponent({
+ props: {
+ diffFile: {
+ ...diffFile,
+ viewer: {
+ ...diffFile.viewer,
+ automaticallyCollapsed: false,
+ manuallyCollapsed: null,
+ },
+ },
+ showLocalFileReviews: true,
+ addMergeRequestButtons: true,
+ expanded: open,
+ },
+ });
+
+ findReviewFileCheckbox().vm.$emit('change', status);
+
+ expect(Boolean(wrapper.emitted().toggleFile)).toBe(fires);
+ },
+ );
+ });
});
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index 7403a7918a9..1d1c5fec293 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import DiffFileRow from '~/diffs/components/diff_file_row.vue';
-import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from '~/diffs/components/file_row_stats.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import FileRow from '~/vue_shared/components/file_row.vue';
describe('Diff File Row component', () => {
let wrapper;
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index c715d779986..9c3c3e82ad5 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -1,26 +1,25 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
-import axios from '~/lib/utils/axios_utils';
-import httpStatus from '~/lib/utils/http_status';
-import createDiffsStore from '~/diffs/store/modules';
-import createNotesStore from '~/notes/stores/modules';
-import diffFileMockDataReadable from '../mock_data/diff_file';
-import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable';
-
+import DiffContentComponent from '~/diffs/components/diff_content.vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import DiffFileHeaderComponent from '~/diffs/components/diff_file_header.vue';
-import DiffContentComponent from '~/diffs/components/diff_content.vue';
-import eventHub from '~/diffs/event_hub';
import {
EVT_EXPAND_ALL_FILES,
EVT_PERF_MARK_DIFF_FILES_END,
EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN,
} from '~/diffs/constants';
+import eventHub from '~/diffs/event_hub';
+import createDiffsStore from '~/diffs/store/modules';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
+import axios from '~/lib/utils/axios_utils';
+import httpStatus from '~/lib/utils/http_status';
+import createNotesStore from '~/notes/stores/modules';
+import diffFileMockDataReadable from '../mock_data/diff_file';
+import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable';
function changeViewer(store, index, { automaticallyCollapsed, manuallyCollapsed, name }) {
const file = store.state.diffs.diffFiles[index];
@@ -66,7 +65,7 @@ function markFileToBeRendered(store, index = 0) {
});
}
-function createComponent({ file, first = false, last = false }) {
+function createComponent({ file, first = false, last = false, options = {}, props = {} }) {
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -89,7 +88,9 @@ function createComponent({ file, first = false, last = false }) {
viewDiffsFileByFile: false,
isFirstFile: first,
isLastFile: last,
+ ...props,
},
+ ...options,
});
return {
@@ -220,6 +221,53 @@ 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 | featureOn | bool
+ ${true} | ${true} | ${true}
+ ${false} | ${true} | ${false}
+ ${true} | ${false} | ${false}
+ ${false} | ${false} | ${false}
+ `(
+ 'should be $bool when { userIsLoggedIn: $loggedIn, featureEnabled: $featureOn }',
+ ({ loggedIn, featureOn, bool }) => {
+ setLoggedIn(loggedIn);
+
+ ({ wrapper } = createComponent({
+ options: {
+ provide: {
+ glFeatures: {
+ localFileReviews: featureOn,
+ },
+ },
+ },
+ props: {
+ file: store.state.diffs.diffFiles[0],
+ },
+ }));
+
+ expect(wrapper.vm.showLocalFileReviews).toBe(bool);
+ },
+ );
+ });
+ });
+
describe('collapsing', () => {
describe(`\`${EVT_EXPAND_ALL_FILES}\` event`, () => {
beforeEach(() => {
@@ -422,9 +470,11 @@ describe('DiffFile', () => {
await wrapper.vm.$nextTick();
- expect(wrapper.vm.$el.innerText).toContain(
- 'This source diff could not be displayed because it is too large',
- );
+ const button = wrapper.find('[data-testid="blob-button"]');
+
+ expect(wrapper.text()).toContain('Changes are too large to be shown.');
+ expect(button.html()).toContain('View file @');
+ expect(button.attributes('href')).toBe('/file/view/path');
});
});
});
diff --git a/spec/frontend/diffs/components/diff_line_note_form_spec.js b/spec/frontend/diffs/components/diff_line_note_form_spec.js
index faa68159c58..a192f7e2e9a 100644
--- a/spec/frontend/diffs/components/diff_line_note_form_spec.js
+++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
-import NoteForm from '~/notes/components/note_form.vue';
import { createStore } from '~/mr_notes/stores';
-import diffFileMockData from '../mock_data/diff_file';
+import NoteForm from '~/notes/components/note_form.vue';
import { noteableDataMock } from '../../notes/mock_data';
+import diffFileMockData from '../mock_data/diff_file';
describe('DiffLineNoteForm', () => {
let wrapper;
@@ -17,6 +17,7 @@ describe('DiffLineNoteForm', () => {
const store = createStore();
store.state.notes.userData.id = 1;
store.state.notes.noteableData = noteableDataMock;
+ store.state.diffs.diffFiles = [diffFile];
store.replaceState({ ...store.state, ...args.state });
diff --git a/spec/frontend/diffs/components/diff_row_spec.js b/spec/frontend/diffs/components/diff_row_spec.js
index c06d8e78316..5682b29d697 100644
--- a/spec/frontend/diffs/components/diff_row_spec.js
+++ b/spec/frontend/diffs/components/diff_row_spec.js
@@ -1,10 +1,10 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getByTestId, fireEvent } from '@testing-library/dom';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import diffsModule from '~/diffs/store/modules';
import DiffRow from '~/diffs/components/diff_row.vue';
-import diffFileMockData from '../mock_data/diff_file';
import { mapParallel } from '~/diffs/components/diff_row_utils';
+import diffsModule from '~/diffs/store/modules';
+import diffFileMockData from '../mock_data/diff_file';
describe('DiffRow', () => {
const testLines = [
diff --git a/spec/frontend/diffs/components/diff_row_utils_spec.js b/spec/frontend/diffs/components/diff_row_utils_spec.js
index d70d6b609ac..47ae3cd5867 100644
--- a/spec/frontend/diffs/components/diff_row_utils_spec.js
+++ b/spec/frontend/diffs/components/diff_row_utils_spec.js
@@ -143,10 +143,21 @@ describe('addCommentTooltip', () => {
'Commenting on symbolic links that replace or are replaced by files is currently not supported.';
const brokenRealTooltip =
'Commenting on files that replace or are replaced by symbolic links is currently not supported.';
+ const commentTooltip = 'Add a comment to this line';
+ const dragTooltip = 'Add a comment to this line or drag for multiple lines';
+
it('should return default tooltip', () => {
expect(utils.addCommentTooltip()).toBeUndefined();
});
+ it('should return comment tooltip', () => {
+ expect(utils.addCommentTooltip({})).toEqual(commentTooltip);
+ });
+
+ it('should return drag comment tooltip when dragging is enabled', () => {
+ expect(utils.addCommentTooltip({}, true)).toEqual(dragTooltip);
+ });
+
it('should return broken symlink tooltip', () => {
expect(utils.addCommentTooltip({ commentsDisabled: { wasSymbolic: true } })).toEqual(
brokenSymLinkTooltip,
diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js
index 0aaec027c0a..504158fb7fc 100644
--- a/spec/frontend/diffs/components/diff_stats_spec.js
+++ b/spec/frontend/diffs/components/diff_stats_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DiffStats from '~/diffs/components/diff_stats.vue';
const TEST_ADDED_LINES = 100;
diff --git a/spec/frontend/diffs/components/diff_view_spec.js b/spec/frontend/diffs/components/diff_view_spec.js
index 3d36ebf14a3..83b173c1f5d 100644
--- a/spec/frontend/diffs/components/diff_view_spec.js
+++ b/spec/frontend/diffs/components/diff_view_spec.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import DiffView from '~/diffs/components/diff_view.vue';
@@ -55,12 +55,12 @@ describe('DiffView', () => {
});
it.each`
- type | side | container | sides | total
- ${'parallel'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {} }, right: { lineDraft: {} } }} | ${2}
- ${'parallel'} | ${'right'} | ${'.new'} | ${{ left: { lineDraft: {} }, right: { lineDraft: {} } }} | ${2}
- ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {} } }} | ${1}
- ${'inline'} | ${'right'} | ${'.new'} | ${{ right: { lineDraft: {} } }} | ${1}
- ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {} }, right: { lineDraft: {} } }} | ${1}
+ type | side | container | sides | total
+ ${'parallel'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {}, renderDiscussion: true }, right: { lineDraft: {}, renderDiscussion: true } }} | ${2}
+ ${'parallel'} | ${'right'} | ${'.new'} | ${{ left: { lineDraft: {}, renderDiscussion: true }, right: { lineDraft: {}, renderDiscussion: true } }} | ${2}
+ ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {}, renderDiscussion: true } }} | ${1}
+ ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {}, renderDiscussion: true } }} | ${1}
+ ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {}, renderDiscussion: true } }} | ${1}
`(
'renders a $type comment row with comment cell on $side',
({ type, container, sides, total }) => {
diff --git a/spec/frontend/diffs/components/image_diff_overlay_spec.js b/spec/frontend/diffs/components/image_diff_overlay_spec.js
index 93c9b922fdd..47b144b2387 100644
--- a/spec/frontend/diffs/components/image_diff_overlay_spec.js
+++ b/spec/frontend/diffs/components/image_diff_overlay_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { createStore } from '~/mr_notes/stores';
import { imageDiffDiscussions } from '../mock_data/diff_discussions';
diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
index 21e7d7397a0..7e6f75ad6f8 100644
--- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
-import { createStore } from '~/mr_notes/stores';
-import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
-import diffFileMockData from '../mock_data/diff_file';
-import discussionsMockData from '../mock_data/diff_discussions';
import { mapInline } from '~/diffs/components/diff_row_utils';
+import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
+import { createStore } from '~/mr_notes/stores';
+import discussionsMockData from '../mock_data/diff_discussions';
+import diffFileMockData from '../mock_data/diff_file';
const TEST_USER_ID = 'abc123';
const TEST_USER = { id: TEST_USER_ID };
diff --git a/spec/frontend/diffs/components/inline_diff_view_spec.js b/spec/frontend/diffs/components/inline_diff_view_spec.js
index 6a1791509fd..27834804f77 100644
--- a/spec/frontend/diffs/components/inline_diff_view_spec.js
+++ b/spec/frontend/diffs/components/inline_diff_view_spec.js
@@ -1,11 +1,11 @@
import '~/behaviors/markdown/render_gfm';
-import { mount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
-import { createStore } from '~/mr_notes/stores';
-import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
+import { mount } from '@vue/test-utils';
import { mapInline } from '~/diffs/components/diff_row_utils';
-import diffFileMockData from '../mock_data/diff_file';
+import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
+import { createStore } from '~/mr_notes/stores';
import discussionsMockData from '../mock_data/diff_discussions';
+import diffFileMockData from '../mock_data/diff_file';
describe('InlineDiffView', () => {
let wrapper;
diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index df9af51f9cf..164c58dc8e4 100644
--- a/spec/frontend/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
@@ -1,8 +1,8 @@
+import { GlButton } from '@gitlab/ui';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlButton } from '@gitlab/ui';
-import { createStore } from '~/mr_notes/stores';
import NoChanges from '~/diffs/components/no_changes.vue';
+import { createStore } from '~/mr_notes/stores';
import diffsMockData from '../mock_data/merge_request_diffs';
const localVue = createLocalVue();
diff --git a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
index 445553706b7..dbe8303077d 100644
--- a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/mr_notes/stores';
-import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
-import { mapParallel } from '~/diffs/components/diff_row_utils';
-import diffFileMockData from '../mock_data/diff_file';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
+import { mapParallel } from '~/diffs/components/diff_row_utils';
+import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
+import { createStore } from '~/mr_notes/stores';
import discussionsMockData from '../mock_data/diff_discussions';
+import diffFileMockData from '../mock_data/diff_file';
describe('ParallelDiffTableRow', () => {
const mockDiffContent = {
diff --git a/spec/frontend/diffs/components/parallel_diff_view_spec.js b/spec/frontend/diffs/components/parallel_diff_view_spec.js
index 44ed303d0ef..452e1f58551 100644
--- a/spec/frontend/diffs/components/parallel_diff_view_spec.js
+++ b/spec/frontend/diffs/components/parallel_diff_view_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { createStore } from '~/mr_notes/stores';
-import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
+import Vuex from 'vuex';
import parallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
+import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
+import { createStore } from '~/mr_notes/stores';
import diffFileMockData from '../mock_data/diff_file';
let wrapper;
diff --git a/spec/frontend/diffs/components/settings_dropdown_spec.js b/spec/frontend/diffs/components/settings_dropdown_spec.js
index fcb627c570a..99fa83b64f1 100644
--- a/spec/frontend/diffs/components/settings_dropdown_spec.js
+++ b/spec/frontend/diffs/components/settings_dropdown_spec.js
@@ -1,6 +1,5 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import diffModule from '~/diffs/store/modules';
import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
import {
EVT_VIEW_FILE_BY_FILE,
@@ -8,6 +7,7 @@ import {
INLINE_DIFF_VIEW_TYPE,
} from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
+import diffModule from '~/diffs/store/modules';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js
index 4666321e0c2..f316a9fdf01 100644
--- a/spec/frontend/diffs/components/tree_list_spec.js
+++ b/spec/frontend/diffs/components/tree_list_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+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';
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 056ac23fcf7..ed3210ecfaf 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import Cookies from 'js-cookie';
-import mockDiffFile from 'jest/diffs/mock_data/diff_file';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
+import mockDiffFile from 'jest/diffs/mock_data/diff_file';
import {
DIFF_VIEW_COOKIE_NAME,
INLINE_DIFF_VIEW_TYPE,
@@ -52,14 +52,14 @@ import {
setFileByFile,
reviewFile,
} from '~/diffs/store/actions';
-import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
-import axios from '~/lib/utils/axios_utils';
import * as utils from '~/diffs/store/utils';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
+import eventHub from '~/notes/event_hub';
import { diffMetadata } from '../mock_data/diff_metadata';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
diff --git a/spec/frontend/diffs/store/getters_spec.js b/spec/frontend/diffs/store/getters_spec.js
index 4d7f861ac22..04606b48662 100644
--- a/spec/frontend/diffs/store/getters_spec.js
+++ b/spec/frontend/diffs/store/getters_spec.js
@@ -1,6 +1,6 @@
+import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
import * as getters from '~/diffs/store/getters';
import state from '~/diffs/store/modules/diff_state';
-import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
import discussion from '../mock_data/diff_discussions';
describe('Diffs Module Getters', () => {
@@ -376,24 +376,62 @@ describe('Diffs Module Getters', () => {
});
});
- describe('fileReviews', () => {
- const file1 = { id: '123', file_identifier_hash: 'abc' };
- const file2 = { id: '098', file_identifier_hash: 'abc' };
+ describe('suggestionCommitMessage', () => {
+ beforeEach(() => {
+ Object.assign(localState, {
+ defaultSuggestionCommitMessage:
+ '%{branch_name}%{project_path}%{project_name}%{username}%{user_full_name}%{file_paths}%{suggestions_count}%{files_count}',
+ branchName: 'branch',
+ projectPath: '/path',
+ projectName: 'name',
+ username: 'user',
+ userFullName: 'user userton',
+ });
+ });
it.each`
- reviews | files | fileReviews
- ${{}} | ${[file1, file2]} | ${[false, false]}
- ${{ abc: ['123'] }} | ${[file1, file2]} | ${[true, false]}
- ${{ abc: ['098'] }} | ${[file1, file2]} | ${[false, true]}
- ${{ def: ['123'] }} | ${[file1, file2]} | ${[false, false]}
- ${{ abc: ['123'], def: ['098'] }} | ${[]} | ${[]}
+ specialState | output
+ ${{}} | ${'branch/pathnameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ userFullName: null }} | ${'branch/pathnameuser%{user_full_name}%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ username: null }} | ${'branch/pathname%{username}user userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ projectName: null }} | ${'branch/path%{project_name}useruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ projectPath: null }} | ${'branch%{project_path}nameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ branchName: null }} | ${'%{branch_name}/pathnameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
`(
- 'returns $fileReviews based on the diff files in state and the existing reviews $reviews',
- ({ reviews, files, fileReviews }) => {
- localState.diffFiles = files;
- localState.mrReviews = reviews;
+ 'provides the correct "base" default commit message based on state ($specialState)',
+ ({ specialState, output }) => {
+ Object.assign(localState, specialState);
+
+ expect(getters.suggestionCommitMessage(localState)()).toBe(output);
+ },
+ );
- expect(getters.fileReviews(localState)).toStrictEqual(fileReviews);
+ it.each`
+ stateOverrides | output
+ ${{}} | ${'branch/pathnameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ user_full_name: null }} | ${'branch/pathnameuser%{user_full_name}%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ username: null }} | ${'branch/pathname%{username}user userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ project_name: null }} | ${'branch/path%{project_name}useruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ project_path: null }} | ${'branch%{project_path}nameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ ${{ branch_name: null }} | ${'%{branch_name}/pathnameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ `(
+ "properly overrides state values ($stateOverrides) if they're provided",
+ ({ stateOverrides, output }) => {
+ expect(getters.suggestionCommitMessage(localState)(stateOverrides)).toBe(output);
+ },
+ );
+
+ it.each`
+ providedValues | output
+ ${{ file_paths: 'path1, path2', suggestions_count: 1, files_count: 1 }} | ${'branch/pathnameuseruser usertonpath1, path211'}
+ ${{ suggestions_count: 1, files_count: 1 }} | ${'branch/pathnameuseruser userton%{file_paths}11'}
+ ${{ file_paths: 'path1, path2', files_count: 1 }} | ${'branch/pathnameuseruser usertonpath1, path2%{suggestions_count}1'}
+ ${{ file_paths: 'path1, path2', suggestions_count: 1 }} | ${'branch/pathnameuseruser usertonpath1, path21%{files_count}'}
+ ${{ something_unused: 'CrAzY TeXt' }} | ${'branch/pathnameuseruser userton%{file_paths}%{suggestions_count}%{files_count}'}
+ `(
+ "fills in any missing interpolations ($providedValues) when they're provided at the getter callsite",
+ ({ providedValues, output }) => {
+ expect(getters.suggestionCommitMessage(localState)(providedValues)).toBe(output);
},
);
});
diff --git a/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js b/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js
index f7954515422..dbef547c297 100644
--- a/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js
+++ b/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js
@@ -1,9 +1,9 @@
-import * as getters from '~/diffs/store/getters';
-import state from '~/diffs/store/modules/diff_state';
import {
DIFF_COMPARE_BASE_VERSION_INDEX,
DIFF_COMPARE_HEAD_VERSION_INDEX,
} from '~/diffs/constants';
+import * as getters from '~/diffs/store/getters';
+import state from '~/diffs/store/modules/diff_state';
import diffsMockData from '../mock_data/merge_request_diffs';
describe('Compare diff version dropdowns', () => {
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index 2c342d8e2a5..a8ae759e693 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -1,9 +1,9 @@
+import { INLINE_DIFF_VIEW_TYPE, INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
import createState from '~/diffs/store/modules/diff_state';
-import mutations from '~/diffs/store/mutations';
import * as types from '~/diffs/store/mutation_types';
-import { INLINE_DIFF_VIEW_TYPE, INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
-import diffFileMockData from '../mock_data/diff_file';
+import mutations from '~/diffs/store/mutations';
import * as utils from '~/diffs/store/utils';
+import diffFileMockData from '../mock_data/diff_file';
describe('DiffsStoreMutations', () => {
describe('SET_BASE_CONFIG', () => {
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index a19e5e91677..dcb58f7a380 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -1,5 +1,4 @@
import { clone } from 'lodash';
-import * as utils from '~/diffs/store/utils';
import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
@@ -12,10 +11,11 @@ import {
INLINE_DIFF_VIEW_TYPE,
INLINE_DIFF_LINES_KEY,
} from '~/diffs/constants';
+import * as utils from '~/diffs/store/utils';
import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants';
+import { noteableDataMock } from '../../notes/mock_data';
import diffFileMockData from '../mock_data/diff_file';
import { diffMetadata } from '../mock_data/diff_metadata';
-import { noteableDataMock } from '../../notes/mock_data';
const getDiffFileMock = () => JSON.parse(JSON.stringify(diffFileMockData));
const getDiffMetadataMock = () => JSON.parse(JSON.stringify(diffMetadata));
diff --git a/spec/frontend/diffs/utils/diff_file_spec.js b/spec/frontend/diffs/utils/diff_file_spec.js
index 2de8db28e71..c6cfdfced65 100644
--- a/spec/frontend/diffs/utils/diff_file_spec.js
+++ b/spec/frontend/diffs/utils/diff_file_spec.js
@@ -1,4 +1,4 @@
-import { prepareRawDiffFile } from '~/diffs/utils/diff_file';
+import { prepareRawDiffFile, getShortShaFromFile } from '~/diffs/utils/diff_file';
function getDiffFiles() {
const loadFull = 'namespace/project/-/merge_requests/12345/diff_for_path?file_identifier=abc';
@@ -143,4 +143,15 @@ describe('diff_file utilities', () => {
expect(preppedFile).not.toHaveProp('id');
});
});
+
+ describe('getShortShaFromFile', () => {
+ it.each`
+ response | cs
+ ${'12345678'} | ${'12345678abcdogcat'}
+ ${null} | ${undefined}
+ ${'hidogcat'} | ${'hidogcatmorethings'}
+ `('returns $response for a file with { content_sha: $cs }', ({ response, cs }) => {
+ expect(getShortShaFromFile({ content_sha: cs })).toBe(response);
+ });
+ });
});
diff --git a/spec/frontend/diffs/utils/file_reviews_spec.js b/spec/frontend/diffs/utils/file_reviews_spec.js
index 819426ee75f..a58c19a7245 100644
--- a/spec/frontend/diffs/utils/file_reviews_spec.js
+++ b/spec/frontend/diffs/utils/file_reviews_spec.js
@@ -5,6 +5,7 @@ import {
setReviewsForMergeRequest,
isFileReviewed,
markFileReview,
+ reviewStatuses,
reviewable,
} from '~/diffs/utils/file_reviews';
@@ -28,6 +29,39 @@ describe('File Review(s) utilities', () => {
localStorage.clear();
});
+ describe('isFileReviewed', () => {
+ it.each`
+ description | diffFile | fileReviews
+ ${'the file does not have an `id`'} | ${{ ...file, id: undefined }} | ${getDefaultReviews()}
+ ${'there are no reviews for the file'} | ${file} | ${{ ...getDefaultReviews(), abc: undefined }}
+ `('returns `false` if $description', ({ diffFile, fileReviews }) => {
+ expect(isFileReviewed(fileReviews, diffFile)).toBe(false);
+ });
+
+ it("returns `true` for a file if it's available in the provided reviews", () => {
+ expect(isFileReviewed(reviews, file)).toBe(true);
+ });
+ });
+
+ describe('reviewStatuses', () => {
+ const file1 = { id: '123', file_identifier_hash: 'abc' };
+ const file2 = { id: '098', file_identifier_hash: 'abc' };
+
+ it.each`
+ mrReviews | files | fileReviews
+ ${{}} | ${[file1, file2]} | ${[false, false]}
+ ${{ abc: ['123'] }} | ${[file1, file2]} | ${[true, false]}
+ ${{ abc: ['098'] }} | ${[file1, file2]} | ${[false, true]}
+ ${{ def: ['123'] }} | ${[file1, file2]} | ${[false, false]}
+ ${{ abc: ['123'], def: ['098'] }} | ${[]} | ${[]}
+ `(
+ 'returns $fileReviews based on the diff files in state and the existing reviews $reviews',
+ ({ mrReviews, files, fileReviews }) => {
+ expect(reviewStatuses(files, mrReviews)).toStrictEqual(fileReviews);
+ },
+ );
+ });
+
describe('getReviewsForMergeRequest', () => {
it('fetches the appropriate stored reviews from localStorage', () => {
getReviewsForMergeRequest(mrPath);
@@ -73,20 +107,6 @@ describe('File Review(s) utilities', () => {
});
});
- describe('isFileReviewed', () => {
- it.each`
- description | diffFile | fileReviews
- ${'the file does not have an `id`'} | ${{ ...file, id: undefined }} | ${getDefaultReviews()}
- ${'there are no reviews for the file'} | ${file} | ${{ ...getDefaultReviews(), abc: undefined }}
- `('returns `false` if $description', ({ diffFile, fileReviews }) => {
- expect(isFileReviewed(fileReviews, diffFile)).toBe(false);
- });
-
- it("returns `true` for a file if it's available in the provided reviews", () => {
- expect(isFileReviewed(reviews, file)).toBe(true);
- });
- });
-
describe('reviewable', () => {
it.each`
response | diffFile | description
diff --git a/spec/frontend/diffs/utils/preferences_spec.js b/spec/frontend/diffs/utils/preferences_spec.js
index a48db1d7512..b09db2c1003 100644
--- a/spec/frontend/diffs/utils/preferences_spec.js
+++ b/spec/frontend/diffs/utils/preferences_spec.js
@@ -1,12 +1,11 @@
import Cookies from 'js-cookie';
-import { getParameterValues } from '~/lib/utils/url_utility';
-
-import { fileByFile } from '~/diffs/utils/preferences';
import {
DIFF_FILE_BY_FILE_COOKIE_NAME,
DIFF_VIEW_FILE_BY_FILE,
DIFF_VIEW_ALL_FILES,
} from '~/diffs/constants';
+import { fileByFile } from '~/diffs/utils/preferences';
+import { getParameterValues } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/diffs/utils/suggestions_spec.js b/spec/frontend/diffs/utils/suggestions_spec.js
new file mode 100644
index 00000000000..fbfe9cef857
--- /dev/null
+++ b/spec/frontend/diffs/utils/suggestions_spec.js
@@ -0,0 +1,15 @@
+import { computeSuggestionCommitMessage } from '~/diffs/utils/suggestions';
+
+describe('Diff Suggestions utilities', () => {
+ describe('computeSuggestionCommitMessage', () => {
+ it.each`
+ description | input | values | output
+ ${'makes the appropriate replacements'} | ${'%{foo} %{bar}'} | ${{ foo: 'foo', bar: 'bar' }} | ${'foo bar'}
+ ${"skips replacing values that aren't passed"} | ${'%{foo} %{bar}'} | ${{ foo: 'foo' }} | ${'foo %{bar}'}
+ ${'treats the number 0 as a valid value (not falsey)'} | ${'%{foo} %{bar}'} | ${{ foo: 'foo', bar: 0 }} | ${'foo 0'}
+ ${"works when the variables don't have any space between them"} | ${'%{foo}%{bar}'} | ${{ foo: 'foo', bar: 'bar' }} | ${'foobar'}
+ `('$description', ({ input, output, values }) => {
+ expect(computeSuggestionCommitMessage({ message: input, values })).toBe(output);
+ });
+ });
+});
diff --git a/spec/frontend/dirty_submit/dirty_submit_factory_spec.js b/spec/frontend/dirty_submit/dirty_submit_factory_spec.js
index 40843a68582..5822ccf861b 100644
--- a/spec/frontend/dirty_submit/dirty_submit_factory_spec.js
+++ b/spec/frontend/dirty_submit/dirty_submit_factory_spec.js
@@ -1,6 +1,6 @@
+import DirtySubmitCollection from '~/dirty_submit/dirty_submit_collection';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
-import DirtySubmitCollection from '~/dirty_submit/dirty_submit_collection';
import { createForm } from './helper';
describe('DirtySubmitCollection', () => {
diff --git a/spec/frontend/droplab/drop_down_spec.js b/spec/frontend/droplab/drop_down_spec.js
index c1fbda6f211..dcdbbcd4ccf 100644
--- a/spec/frontend/droplab/drop_down_spec.js
+++ b/spec/frontend/droplab/drop_down_spec.js
@@ -1,6 +1,6 @@
+import { SELECTED_CLASS } from '~/droplab/constants';
import DropDown from '~/droplab/drop_down';
import utils from '~/droplab/utils';
-import { SELECTED_CLASS } from '~/droplab/constants';
describe('DropLab DropDown', () => {
let testContext;
diff --git a/spec/frontend/droplab/hook_spec.js b/spec/frontend/droplab/hook_spec.js
index 11488cab521..0b897a570f6 100644
--- a/spec/frontend/droplab/hook_spec.js
+++ b/spec/frontend/droplab/hook_spec.js
@@ -1,5 +1,5 @@
-import Hook from '~/droplab/hook';
import DropDown from '~/droplab/drop_down';
+import Hook from '~/droplab/hook';
jest.mock('~/droplab/drop_down', () => jest.fn());
diff --git a/spec/frontend/droplab/plugins/ajax_filter_spec.js b/spec/frontend/droplab/plugins/ajax_filter_spec.js
index d91884a60e5..d442d5cf416 100644
--- a/spec/frontend/droplab/plugins/ajax_filter_spec.js
+++ b/spec/frontend/droplab/plugins/ajax_filter_spec.js
@@ -1,5 +1,5 @@
-import AjaxCache from '~/lib/utils/ajax_cache';
import AjaxFilter from '~/droplab/plugins/ajax_filter';
+import AjaxCache from '~/lib/utils/ajax_cache';
describe('AjaxFilter', () => {
let dummyConfig;
diff --git a/spec/frontend/droplab/plugins/ajax_spec.js b/spec/frontend/droplab/plugins/ajax_spec.js
index 1d7576ce420..7c6452e8337 100644
--- a/spec/frontend/droplab/plugins/ajax_spec.js
+++ b/spec/frontend/droplab/plugins/ajax_spec.js
@@ -1,5 +1,5 @@
-import AjaxCache from '~/lib/utils/ajax_cache';
import Ajax from '~/droplab/plugins/ajax';
+import AjaxCache from '~/lib/utils/ajax_cache';
describe('Ajax', () => {
describe('preprocessing', () => {
diff --git a/spec/frontend/dropzone_input_spec.js b/spec/frontend/dropzone_input_spec.js
index 4cfc6478bd2..5e6ccbd7cda 100644
--- a/spec/frontend/dropzone_input_spec.js
+++ b/spec/frontend/dropzone_input_spec.js
@@ -1,9 +1,9 @@
import $ from 'jquery';
import mock from 'xhr-mock';
-import { TEST_HOST } from 'spec/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
-import dropzoneInput from '~/dropzone_input';
+import { TEST_HOST } from 'spec/test_constants';
import PasteMarkdownTable from '~/behaviors/markdown/paste_markdown_table';
+import dropzoneInput from '~/dropzone_input';
const TEST_FILE = new File([], 'somefile.jpg');
TEST_FILE.upload = {};
diff --git a/spec/frontend/editor/editor_ci_schema_ext_spec.js b/spec/frontend/editor/editor_ci_schema_ext_spec.js
index 9dd88aad7e6..17a9ae7335f 100644
--- a/spec/frontend/editor/editor_ci_schema_ext_spec.js
+++ b/spec/frontend/editor/editor_ci_schema_ext_spec.js
@@ -1,8 +1,8 @@
import { languages } from 'monaco-editor';
import { TEST_HOST } from 'helpers/test_constants';
+import { EXTENSION_CI_SCHEMA_FILE_NAME_MATCH } from '~/editor/constants';
import EditorLite from '~/editor/editor_lite';
import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
-import { EXTENSION_CI_SCHEMA_FILE_NAME_MATCH } from '~/editor/constants';
describe('~/editor/editor_ci_config_ext', () => {
const defaultBlobPath = '.gitlab-ci.yml';
diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js
index c3099997287..815457e012f 100644
--- a/spec/frontend/editor/editor_lite_spec.js
+++ b/spec/frontend/editor/editor_lite_spec.js
@@ -1,14 +1,21 @@
/* eslint-disable max-classes-per-file */
-import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
+import { editor as monacoEditor, languages as monacoLanguages } from 'monaco-editor';
import waitForPromises from 'helpers/wait_for_promises';
-import Editor from '~/editor/editor_lite';
+import {
+ EDITOR_LITE_INSTANCE_ERROR_NO_EL,
+ URI_PREFIX,
+ EDITOR_READY_EVENT,
+} from '~/editor/constants';
+import EditorLite from '~/editor/editor_lite';
import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
-import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from '~/editor/constants';
+import { joinPaths } from '~/lib/utils/url_utility';
describe('Base editor', () => {
let editorEl;
let editor;
+ let defaultArguments;
+ const blobOriginalContent = 'Foo Foo';
const blobContent = 'Foo Bar';
const blobPath = 'test.md';
const blobGlobalId = 'snippet_777';
@@ -17,15 +24,19 @@ describe('Base editor', () => {
beforeEach(() => {
setFixtures('<div id="editor" data-editor-loading></div>');
editorEl = document.getElementById('editor');
- editor = new Editor();
+ defaultArguments = { el: editorEl, blobPath, blobContent, blobGlobalId };
+ editor = new EditorLite();
});
afterEach(() => {
editor.dispose();
editorEl.remove();
+ monacoEditor.getModels().forEach((model) => {
+ model.dispose();
+ });
});
- const createUri = (...paths) => Uri.file([URI_PREFIX, ...paths].join('/'));
+ const uriFilePath = joinPaths('/', URI_PREFIX, blobGlobalId, blobPath);
it('initializes Editor with basic properties', () => {
expect(editor).toBeDefined();
@@ -38,76 +49,192 @@ describe('Base editor', () => {
expect(editorEl.dataset.editorLoading).toBeUndefined();
});
- describe('instance of the Editor', () => {
+ describe('instance of the Editor Lite', () => {
let modelSpy;
let instanceSpy;
- let setModel;
- let dispose;
+ const setModel = jest.fn();
+ const dispose = jest.fn();
+ const mockModelReturn = (res = fakeModel) => {
+ modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => res);
+ };
+ const mockDecorateInstance = (decorations = {}) => {
+ jest.spyOn(EditorLite, 'convertMonacoToELInstance').mockImplementation((inst) => {
+ return Object.assign(inst, decorations);
+ });
+ };
beforeEach(() => {
- setModel = jest.fn();
- dispose = jest.fn();
- modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => fakeModel);
- instanceSpy = jest.spyOn(monacoEditor, 'create').mockImplementation(() => ({
- setModel,
- dispose,
- onDidDispose: jest.fn(),
- }));
+ modelSpy = jest.spyOn(monacoEditor, 'createModel');
});
- it('throws an error if no dom element is supplied', () => {
- expect(() => {
- editor.createInstance();
- }).toThrow(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
+ describe('instance of the Code Editor', () => {
+ beforeEach(() => {
+ instanceSpy = jest.spyOn(monacoEditor, 'create');
+ });
- expect(modelSpy).not.toHaveBeenCalled();
- expect(instanceSpy).not.toHaveBeenCalled();
- expect(setModel).not.toHaveBeenCalled();
- });
+ it('throws an error if no dom element is supplied', () => {
+ mockDecorateInstance();
+ expect(() => {
+ editor.createInstance();
+ }).toThrow(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
- it('creates model to be supplied to Monaco editor', () => {
- editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId: '' });
+ expect(modelSpy).not.toHaveBeenCalled();
+ expect(instanceSpy).not.toHaveBeenCalled();
+ expect(EditorLite.convertMonacoToELInstance).not.toHaveBeenCalled();
+ });
- expect(modelSpy).toHaveBeenCalledWith(blobContent, undefined, createUri(blobPath));
- expect(setModel).toHaveBeenCalledWith(fakeModel);
- });
+ it('creates model to be supplied to Monaco editor', () => {
+ mockModelReturn();
+ mockDecorateInstance({
+ setModel,
+ });
+ editor.createInstance(defaultArguments);
- it('initializes the instance on a supplied DOM node', () => {
- editor.createInstance({ el: editorEl });
+ expect(modelSpy).toHaveBeenCalledWith(
+ blobContent,
+ undefined,
+ expect.objectContaining({
+ path: uriFilePath,
+ }),
+ );
+ expect(setModel).toHaveBeenCalledWith(fakeModel);
+ });
- expect(editor.editorEl).not.toBe(null);
- expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything());
- });
+ it('does not create a model automatically if model is passed as `null`', () => {
+ mockDecorateInstance({
+ setModel,
+ });
+ editor.createInstance({ ...defaultArguments, model: null });
+ expect(modelSpy).not.toHaveBeenCalled();
+ expect(setModel).not.toHaveBeenCalled();
+ });
- it('with blobGlobalId, creates model with id in uri', () => {
- editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId });
+ it('initializes the instance on a supplied DOM node', () => {
+ editor.createInstance({ el: editorEl });
- expect(modelSpy).toHaveBeenCalledWith(
- blobContent,
- undefined,
- createUri(blobGlobalId, blobPath),
- );
- });
+ expect(editor.editorEl).not.toBe(null);
+ expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything());
+ });
- it('initializes instance with passed properties', () => {
- const instanceOptions = {
- foo: 'bar',
- };
- editor.createInstance({
- el: editorEl,
- ...instanceOptions,
+ it('with blobGlobalId, creates model with the id in uri', () => {
+ editor.createInstance(defaultArguments);
+
+ expect(modelSpy).toHaveBeenCalledWith(
+ blobContent,
+ undefined,
+ expect.objectContaining({
+ path: uriFilePath,
+ }),
+ );
+ });
+
+ it('initializes instance with passed properties', () => {
+ const instanceOptions = {
+ foo: 'bar',
+ };
+ editor.createInstance({
+ el: editorEl,
+ ...instanceOptions,
+ });
+ expect(instanceSpy).toHaveBeenCalledWith(
+ editorEl,
+ expect.objectContaining(instanceOptions),
+ );
+ });
+
+ it('disposes instance when the global editor is disposed', () => {
+ mockDecorateInstance({
+ dispose,
+ });
+ editor.createInstance(defaultArguments);
+
+ expect(dispose).not.toHaveBeenCalled();
+
+ editor.dispose();
+
+ expect(dispose).toHaveBeenCalled();
+ });
+
+ it("removes the disposed instance from the global editor's storage and disposes the associated model", () => {
+ mockModelReturn();
+ mockDecorateInstance({
+ setModel,
+ });
+ const instance = editor.createInstance(defaultArguments);
+
+ expect(editor.instances).toHaveLength(1);
+ expect(fakeModel.dispose).not.toHaveBeenCalled();
+
+ instance.dispose();
+
+ expect(editor.instances).toHaveLength(0);
+ expect(fakeModel.dispose).toHaveBeenCalled();
});
- expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.objectContaining(instanceOptions));
});
- it('disposes instance when the editor is disposed', () => {
- editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId });
+ describe('instance of the Diff Editor', () => {
+ beforeEach(() => {
+ instanceSpy = jest.spyOn(monacoEditor, 'createDiffEditor');
+ });
- expect(dispose).not.toHaveBeenCalled();
+ it('Diff Editor goes through the normal path of Code Editor just with the flag ON', () => {
+ const spy = jest.spyOn(editor, 'createInstance').mockImplementation(() => {});
+ editor.createDiffInstance();
+ expect(spy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ isDiff: true,
+ }),
+ );
+ });
- editor.dispose();
+ it('initializes the instance on a supplied DOM node', () => {
+ const wrongInstanceSpy = jest.spyOn(monacoEditor, 'create').mockImplementation(() => ({}));
+ editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
+
+ expect(editor.editorEl).not.toBe(null);
+ expect(wrongInstanceSpy).not.toHaveBeenCalled();
+ expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything());
+ });
+
+ it('creates correct model for the Diff Editor', () => {
+ const instance = editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
+ const getDiffModelValue = (model) => instance.getModel()[model].getValue();
+
+ expect(modelSpy).toHaveBeenCalledTimes(2);
+ expect(modelSpy.mock.calls[0]).toEqual([
+ blobContent,
+ undefined,
+ expect.objectContaining({
+ path: uriFilePath,
+ }),
+ ]);
+ expect(modelSpy.mock.calls[1]).toEqual([blobOriginalContent, 'markdown']);
+ expect(getDiffModelValue('original')).toBe(blobOriginalContent);
+ expect(getDiffModelValue('modified')).toBe(blobContent);
+ });
- expect(dispose).toHaveBeenCalled();
+ it('correctly disposes the diff editor model', () => {
+ const modifiedModel = fakeModel;
+ const originalModel = { ...fakeModel };
+ mockDecorateInstance({
+ getModel: jest.fn().mockReturnValue({
+ original: originalModel,
+ modified: modifiedModel,
+ }),
+ });
+
+ const instance = editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
+
+ expect(editor.instances).toHaveLength(1);
+ expect(originalModel.dispose).not.toHaveBeenCalled();
+ expect(modifiedModel.dispose).not.toHaveBeenCalled();
+
+ instance.dispose();
+
+ expect(editor.instances).toHaveLength(0);
+ expect(originalModel.dispose).toHaveBeenCalled();
+ expect(modifiedModel.dispose).toHaveBeenCalled();
+ });
});
});
@@ -127,16 +254,14 @@ describe('Base editor', () => {
editorEl2 = document.getElementById('editor2');
inst1Args = {
el: editorEl1,
- blobGlobalId,
};
inst2Args = {
el: editorEl2,
blobContent,
blobPath,
- blobGlobalId,
};
- editor = new Editor();
+ editor = new EditorLite();
instanceSpy = jest.spyOn(monacoEditor, 'create');
});
@@ -166,8 +291,20 @@ describe('Base editor', () => {
expect(model1).not.toEqual(model2);
});
+ it('does not create a new model if a model for the path & globalId combo already exists', () => {
+ const modelSpy = jest.spyOn(monacoEditor, 'createModel');
+ inst1 = editor.createInstance({ ...inst2Args, blobGlobalId });
+ inst2 = editor.createInstance({ ...inst2Args, el: editorEl1, blobGlobalId });
+
+ const model1 = inst1.getModel();
+ const model2 = inst2.getModel();
+
+ expect(modelSpy).toHaveBeenCalledTimes(1);
+ expect(model1).toBe(model2);
+ });
+
it('shares global editor options among all instances', () => {
- editor = new Editor({
+ editor = new EditorLite({
readOnly: true,
});
@@ -179,7 +316,7 @@ describe('Base editor', () => {
});
it('allows overriding editor options on the instance level', () => {
- editor = new Editor({
+ editor = new EditorLite({
readOnly: true,
});
inst1 = editor.createInstance({
@@ -200,6 +337,7 @@ describe('Base editor', () => {
expect(monacoEditor.getModels()).toHaveLength(2);
inst1.dispose();
+
expect(inst1.getModel()).toBe(null);
expect(inst2.getModel()).not.toBe(null);
expect(editor.instances).toHaveLength(1);
@@ -402,19 +540,20 @@ describe('Base editor', () => {
el: editorEl,
blobPath,
blobContent,
- blobGlobalId,
extensions,
});
};
beforeEach(() => {
- editorExtensionSpy = jest.spyOn(Editor, 'pushToImportsArray').mockImplementation((arr) => {
- arr.push(
- Promise.resolve({
- default: {},
- }),
- );
- });
+ editorExtensionSpy = jest
+ .spyOn(EditorLite, 'pushToImportsArray')
+ .mockImplementation((arr) => {
+ arr.push(
+ Promise.resolve({
+ default: {},
+ }),
+ );
+ });
});
it.each([undefined, [], [''], ''])(
@@ -446,15 +585,20 @@ describe('Base editor', () => {
expect(editorExtensionSpy).toHaveBeenCalledWith(expect.any(Array), expectation);
});
- it('emits editor-ready event after all extensions were applied', async () => {
+ it('emits EDITOR_READY_EVENT event after all extensions were applied', async () => {
const calls = [];
const eventSpy = jest.fn().mockImplementation(() => {
calls.push('event');
});
- const useSpy = jest.spyOn(editor, 'use').mockImplementation(() => {
+ const useSpy = jest.fn().mockImplementation(() => {
calls.push('use');
});
- editorEl.addEventListener('editor-ready', eventSpy);
+ jest.spyOn(EditorLite, 'convertMonacoToELInstance').mockImplementation((inst) => {
+ const decoratedInstance = inst;
+ decoratedInstance.use = useSpy;
+ return decoratedInstance;
+ });
+ editorEl.addEventListener(EDITOR_READY_EVENT, eventSpy);
instance = instanceConstructor('foo, bar');
await waitForPromises();
expect(useSpy.mock.calls).toHaveLength(2);
@@ -487,12 +631,6 @@ describe('Base editor', () => {
expect(inst1.alpha()).toEqual(alphaRes);
expect(inst2.alpha()).toEqual(alphaRes);
});
-
- it('extends specific instance if it has been passed', () => {
- editor.use(AlphaExt, inst2);
- expect(inst1.alpha).toBeUndefined();
- expect(inst2.alpha()).toEqual(alphaRes);
- });
});
});
@@ -526,7 +664,7 @@ describe('Base editor', () => {
it('sets default syntax highlighting theme', () => {
const expectedTheme = themes.find((t) => t.name === DEFAULT_THEME);
- editor = new Editor();
+ editor = new EditorLite();
expect(themeDefineSpy).toHaveBeenCalledWith(DEFAULT_THEME, expectedTheme.data);
expect(themeSetSpy).toHaveBeenCalledWith(DEFAULT_THEME);
@@ -538,7 +676,7 @@ describe('Base editor', () => {
expect(expectedTheme.name).not.toBe(DEFAULT_THEME);
window.gon.user_color_scheme = expectedTheme.name;
- editor = new Editor();
+ editor = new EditorLite();
expect(themeDefineSpy).toHaveBeenCalledWith(expectedTheme.name, expectedTheme.data);
expect(themeSetSpy).toHaveBeenCalledWith(expectedTheme.name);
@@ -549,7 +687,7 @@ describe('Base editor', () => {
const nonExistentTheme = { name };
window.gon.user_color_scheme = nonExistentTheme.name;
- editor = new Editor();
+ editor = new EditorLite();
expect(themeDefineSpy).not.toHaveBeenCalled();
expect(themeSetSpy).toHaveBeenCalledWith(DEFAULT_THEME);
diff --git a/spec/frontend/emoji/emoji_spec.js b/spec/frontend/emoji/index_spec.js
index feec445bc8d..1e6f5483160 100644
--- a/spec/frontend/emoji/emoji_spec.js
+++ b/spec/frontend/emoji/index_spec.js
@@ -1,6 +1,6 @@
+import { emojiFixtureMap, mockEmojiData, initEmojiMock } from 'helpers/emoji';
import { trimText } from 'helpers/text_helper';
-import { emojiFixtureMap, initEmojiMock, describeEmojiFields } from 'helpers/emoji';
-import { glEmojiTag, searchEmoji, getEmoji } from '~/emoji';
+import { glEmojiTag, searchEmoji, getEmojiInfo, sortEmoji } from '~/emoji';
import isEmojiUnicodeSupported, {
isFlagEmoji,
isRainbowFlagEmoji,
@@ -29,7 +29,7 @@ const emptySupportMap = {
1.1: false,
};
-describe('gl_emoji', () => {
+describe('emoji', () => {
let mock;
beforeEach(async () => {
@@ -43,7 +43,7 @@ describe('gl_emoji', () => {
describe('glEmojiTag', () => {
it('bomb emoji', () => {
const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
+ const markup = glEmojiTag(emojiKey);
expect(trimText(markup)).toMatchInlineSnapshot(
`"<gl-emoji data-name=\\"bomb\\"></gl-emoji>"`,
@@ -52,7 +52,7 @@ describe('gl_emoji', () => {
it('bomb emoji with sprite fallback readiness', () => {
const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
+ const markup = glEmojiTag(emojiKey, {
sprite: true,
});
expect(trimText(markup)).toMatchInlineSnapshot(
@@ -352,125 +352,272 @@ describe('gl_emoji', () => {
});
});
- describe('getEmoji', () => {
- const { grey_question } = emojiFixtureMap;
-
- describe('when query is undefined', () => {
- it('should return null by default', () => {
- expect(getEmoji()).toBe(null);
- });
-
- it('should return fallback emoji when fallback is true', () => {
- expect(getEmoji(undefined, true).name).toEqual(grey_question.name);
- });
+ describe('getEmojiInfo', () => {
+ it.each(['atom', 'five', 'black_heart'])("should return a correct emoji for '%s'", (name) => {
+ expect(getEmojiInfo(name)).toEqual(mockEmojiData[name]);
});
- });
-
- describe('searchEmoji', () => {
- const { atom, grey_question } = emojiFixtureMap;
- const search = (query, opts) => searchEmoji(query, opts).map(({ name }) => name);
- const mangle = (str) => str.slice(0, 1) + str.slice(-1);
- const partial = (str) => str.slice(0, 2);
-
- describe('with default options', () => {
- const subject = (query) => search(query);
-
- describeEmojiFields('with $field', ({ accessor }) => {
- it(`should match by lower case: ${accessor(atom)}`, () => {
- expect(subject(accessor(atom))).toContain(atom.name);
- });
-
- it(`should match by upper case: ${accessor(atom).toUpperCase()}`, () => {
- expect(subject(accessor(atom).toUpperCase())).toContain(atom.name);
- });
-
- it(`should not match by partial: ${mangle(accessor(atom))}`, () => {
- expect(subject(mangle(accessor(atom)))).not.toContain(atom.name);
- });
- });
-
- it(`should match by unicode value: ${atom.moji}`, () => {
- expect(subject(atom.moji)).toContain(atom.name);
- });
-
- it('should not return a fallback value', () => {
- expect(subject('foo bar baz')).toHaveLength(0);
- });
- it('should not return a fallback value when query is falsey', () => {
- expect(subject()).toHaveLength(0);
- });
+ it('should return fallback emoji by default', () => {
+ expect(getEmojiInfo('atjs')).toEqual(mockEmojiData.grey_question);
});
- describe('with fuzzy match', () => {
- const subject = (query) => search(query, { match: 'fuzzy' });
-
- describeEmojiFields('with $field', ({ accessor }) => {
- it(`should match by lower case: ${accessor(atom)}`, () => {
- expect(subject(accessor(atom))).toContain(atom.name);
- });
-
- it(`should match by upper case: ${accessor(atom).toUpperCase()}`, () => {
- expect(subject(accessor(atom).toUpperCase())).toContain(atom.name);
- });
-
- it(`should match by partial: ${mangle(accessor(atom))}`, () => {
- expect(subject(mangle(accessor(atom)))).toContain(atom.name);
- });
- });
+ it('should return null when fallback is false', () => {
+ expect(getEmojiInfo('atjs', false)).toBe(null);
});
- describe('with contains match', () => {
- const subject = (query) => search(query, { match: 'contains' });
-
- describeEmojiFields('with $field', ({ accessor }) => {
- it(`should match by lower case: ${accessor(atom)}`, () => {
- expect(subject(accessor(atom))).toContain(atom.name);
- });
-
- it(`should match by upper case: ${accessor(atom).toUpperCase()}`, () => {
- expect(subject(accessor(atom).toUpperCase())).toContain(atom.name);
- });
-
- it(`should match by partial: ${partial(accessor(atom))}`, () => {
- expect(subject(partial(accessor(atom)))).toContain(atom.name);
- });
-
- it(`should not match by mangled: ${mangle(accessor(atom))}`, () => {
- expect(subject(mangle(accessor(atom)))).not.toContain(atom.name);
- });
+ describe('when query is undefined', () => {
+ it('should return fallback emoji by default', () => {
+ expect(getEmojiInfo()).toEqual(mockEmojiData.grey_question);
});
- });
- describe('with fallback', () => {
- const subject = (query) => search(query, { fallback: true });
-
- it.each`
- query
- ${'foo bar baz'} | ${undefined}
- `('should return a fallback value when given $query', ({ query }) => {
- expect(subject(query)).toContain(grey_question.name);
+ it('should return null when fallback is false', () => {
+ expect(getEmojiInfo(undefined, false)).toBe(null);
});
});
+ });
- describe('with name and alias fields', () => {
- const subject = (query) => search(query, { fields: ['name', 'alias'] });
-
- it(`should match by name: ${atom.name}`, () => {
- expect(subject(atom.name)).toContain(atom.name);
+ describe('searchEmoji', () => {
+ const emojiFixture = Object.keys(mockEmojiData).reduce((acc, k) => {
+ const { name, e, u, d } = mockEmojiData[k];
+ acc[k] = { name, e, u, d };
+
+ return acc;
+ }, {});
+
+ it.each([undefined, null, ''])("should return all emoji when the input is '%s'", (input) => {
+ const search = searchEmoji(input);
+
+ const expected = [
+ 'atom',
+ 'bomb',
+ 'construction_worker_tone5',
+ 'five',
+ 'grey_question',
+ 'black_heart',
+ 'heart',
+ 'custard',
+ 'star',
+ ].map((name) => {
+ return {
+ emoji: emojiFixture[name],
+ field: 'd',
+ fieldValue: emojiFixture[name].d,
+ score: 0,
+ };
});
- it(`should match by alias: ${atom.aliases[0]}`, () => {
- expect(subject(atom.aliases[0])).toContain(atom.name);
+ expect(search).toEqual(expected);
+ });
+
+ it.each([
+ [
+ 'searching by unicode value',
+ '⚛',
+ [
+ {
+ name: 'atom',
+ field: 'e',
+ fieldValue: 'atom',
+ score: 0,
+ },
+ ],
+ ],
+ [
+ 'searching by partial alias',
+ '_symbol',
+ [
+ {
+ name: 'atom',
+ field: 'alias',
+ fieldValue: 'atom_symbol',
+ score: 4,
+ },
+ ],
+ ],
+ [
+ 'searching by full alias',
+ 'atom_symbol',
+ [
+ {
+ name: 'atom',
+ field: 'alias',
+ fieldValue: 'atom_symbol',
+ score: 0,
+ },
+ ],
+ ],
+ ])('should return a correct result when %s', (_, query, searchResult) => {
+ const expected = searchResult.map((item) => {
+ const { field, score, fieldValue, name } = item;
+
+ return {
+ emoji: emojiFixture[name],
+ field,
+ fieldValue,
+ score,
+ };
});
- it(`should not match by description: ${atom.description}`, () => {
- expect(subject(atom.description)).not.toContain(atom.name);
+ expect(searchEmoji(query)).toEqual(expected);
+ });
+
+ it.each([
+ ['searching with a non-existing emoji name', 'asdf', []],
+ [
+ 'searching by full name',
+ 'atom',
+ [
+ {
+ name: 'atom',
+ field: 'd',
+ score: 0,
+ },
+ ],
+ ],
+
+ [
+ 'searching by full description',
+ 'atom symbol',
+ [
+ {
+ name: 'atom',
+ field: 'd',
+ score: 0,
+ },
+ ],
+ ],
+
+ [
+ 'searching by partial name',
+ 'question',
+ [
+ {
+ name: 'grey_question',
+ field: 'name',
+ score: 5,
+ },
+ ],
+ ],
+ [
+ 'searching by partial description',
+ 'ment',
+ [
+ {
+ name: 'grey_question',
+ field: 'd',
+ score: 24,
+ },
+ ],
+ ],
+ [
+ 'searching with query "heart"',
+ 'heart',
+ [
+ {
+ name: 'black_heart',
+ field: 'd',
+ score: 6,
+ },
+ {
+ name: 'heart',
+ field: 'name',
+ score: 0,
+ },
+ ],
+ ],
+ [
+ 'searching with query "HEART"',
+ 'HEART',
+ [
+ {
+ name: 'black_heart',
+ field: 'd',
+ score: 6,
+ },
+ {
+ name: 'heart',
+ field: 'name',
+ score: 0,
+ },
+ ],
+ ],
+ [
+ 'searching with query "star"',
+ 'star',
+ [
+ {
+ name: 'custard',
+ field: 'd',
+ score: 2,
+ },
+ {
+ name: 'star',
+ field: 'name',
+ score: 0,
+ },
+ ],
+ ],
+ ])('should return a correct result when %s', (_, query, searchResult) => {
+ const expected = searchResult.map((item) => {
+ const { field, score, name } = item;
+
+ return {
+ emoji: emojiFixture[name],
+ field,
+ fieldValue: emojiFixture[name][field],
+ score,
+ };
});
- it(`should not match by unicode value: ${atom.moji}`, () => {
- expect(subject(atom.moji)).not.toContain(atom.name);
- });
+ expect(searchEmoji(query)).toEqual(expected);
+ });
+ });
+
+ describe('sortEmoji', () => {
+ const testCases = [
+ [
+ 'should correctly sort by score',
+ [
+ { score: 10, fieldValue: '', emoji: { name: 'a' } },
+ { score: 5, fieldValue: '', emoji: { name: 'b' } },
+ { score: 0, fieldValue: '', emoji: { name: 'c' } },
+ ],
+ [
+ { score: 0, fieldValue: '', emoji: { name: 'c' } },
+ { score: 5, fieldValue: '', emoji: { name: 'b' } },
+ { score: 10, fieldValue: '', emoji: { name: 'a' } },
+ ],
+ ],
+ [
+ 'should correctly sort by fieldValue',
+ [
+ { score: 0, fieldValue: 'y', emoji: { name: 'b' } },
+ { score: 0, fieldValue: 'x', emoji: { name: 'a' } },
+ { score: 0, fieldValue: 'z', emoji: { name: 'c' } },
+ ],
+ [
+ { score: 0, fieldValue: 'x', emoji: { name: 'a' } },
+ { score: 0, fieldValue: 'y', emoji: { name: 'b' } },
+ { score: 0, fieldValue: 'z', emoji: { name: 'c' } },
+ ],
+ ],
+ [
+ 'should correctly sort by score and then by fieldValue (in order)',
+ [
+ { score: 5, fieldValue: 'y', emoji: { name: 'c' } },
+ { score: 0, fieldValue: 'z', emoji: { name: 'a' } },
+ { score: 5, fieldValue: 'x', emoji: { name: 'b' } },
+ ],
+ [
+ { score: 0, fieldValue: 'z', emoji: { name: 'a' } },
+ { score: 5, fieldValue: 'x', emoji: { name: 'b' } },
+ { score: 5, fieldValue: 'y', emoji: { name: 'c' } },
+ ],
+ ],
+ ];
+
+ it.each(testCases)('%s', (_, scoredItems, expected) => {
+ expect(sortEmoji(scoredItems)).toEqual(expected);
});
});
});
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index c055702d832..d1bc11538a3 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -1,8 +1,12 @@
/* eslint-disable import/no-commonjs, max-classes-per-file */
const path = require('path');
-const { ErrorWithStack } = require('jest-util');
const JSDOMEnvironment = require('jest-environment-jsdom');
+const { ErrorWithStack } = require('jest-util');
+const {
+ setGlobalDateToFakeDate,
+ setGlobalDateToRealDate,
+} = require('./__helpers__/fake_date/fake_date');
const { TEST_HOST } = require('./__helpers__/test_constants');
const ROOT_PATH = path.resolve(__dirname, '../..');
@@ -12,6 +16,10 @@ class CustomEnvironment extends JSDOMEnvironment {
// Setup testURL so that window.location is setup properly
super({ ...config, testURL: TEST_HOST }, context);
+ // Fake the `Date` for `jsdom` which fixes things like document.cookie
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39496#note_503084332
+ setGlobalDateToFakeDate();
+
Object.assign(context.console, {
error(...args) {
throw new ErrorWithStack(
@@ -69,6 +77,9 @@ class CustomEnvironment extends JSDOMEnvironment {
}
async teardown() {
+ // Reset `Date` so that Jest can report timing accurately *roll eyes*...
+ setGlobalDateToRealDate();
+
await new Promise(setImmediate);
if (this.rejectedPromises.length > 0) {
diff --git a/spec/frontend/environments/canary_ingress_spec.js b/spec/frontend/environments/canary_ingress_spec.js
index 3dd67de1369..6c7a786e652 100644
--- a/spec/frontend/environments/canary_ingress_spec.js
+++ b/spec/frontend/environments/canary_ingress_spec.js
@@ -1,8 +1,8 @@
-import { mount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { CANARY_UPDATE_MODAL } from '~/environments/constants';
import CanaryIngress from '~/environments/components/canary_ingress.vue';
+import { CANARY_UPDATE_MODAL } from '~/environments/constants';
describe('/environments/components/canary_ingress.vue', () => {
let wrapper;
diff --git a/spec/frontend/environments/canary_update_modal_spec.js b/spec/frontend/environments/canary_update_modal_spec.js
index d0b97cf2eda..c7129ee1320 100644
--- a/spec/frontend/environments/canary_update_modal_spec.js
+++ b/spec/frontend/environments/canary_update_modal_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlAlert, GlModal } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import updateCanaryIngress from '~/environments/graphql/mutations/update_canary_ingress.mutation.graphql';
diff --git a/spec/frontend/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js
index a1a22274e8f..8fb53579f96 100644
--- a/spec/frontend/environments/confirm_rollback_modal_spec.js
+++ b/spec/frontend/environments/confirm_rollback_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
import eventHub from '~/environments/event_hub';
diff --git a/spec/frontend/environments/deploy_board_component_spec.js b/spec/frontend/environments/deploy_board_component_spec.js
index dbef03f99d8..53220341a62 100644
--- a/spec/frontend/environments/deploy_board_component_spec.js
+++ b/spec/frontend/environments/deploy_board_component_spec.js
@@ -1,8 +1,8 @@
import { GlTooltip, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
-import DeployBoard from '~/environments/components/deploy_board.vue';
import CanaryIngress from '~/environments/components/canary_ingress.vue';
+import DeployBoard from '~/environments/components/deploy_board.vue';
import { deployBoardMockData, environment } from './mock_data';
const logsPath = `gitlab-org/gitlab-test/-/logs?environment_name=${environment.name}`;
diff --git a/spec/frontend/environments/enable_review_app_modal_spec.js b/spec/frontend/environments/enable_review_app_modal_spec.js
index 7ea49a6e1d0..f5063cff620 100644
--- a/spec/frontend/environments/enable_review_app_modal_spec.js
+++ b/spec/frontend/environments/enable_review_app_modal_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import EnableReviewAppButton from '~/environments/components/enable_review_app_modal.vue';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
describe('Enable Review App Button', () => {
let wrapper;
diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js
index 875a01c07ea..db78a6b0cdd 100644
--- a/spec/frontend/environments/environment_actions_spec.js
+++ b/spec/frontend/environments/environment_actions_spec.js
@@ -1,9 +1,9 @@
+import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
-import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import eventHub from '~/environments/event_hub';
import EnvironmentActions from '~/environments/components/environment_actions.vue';
+import eventHub from '~/environments/event_hub';
const scheduledJobAction = {
name: 'scheduled action',
diff --git a/spec/frontend/environments/environment_item_spec.js b/spec/frontend/environments/environment_item_spec.js
index bc692352103..09ab1223fd1 100644
--- a/spec/frontend/environments/environment_item_spec.js
+++ b/spec/frontend/environments/environment_item_spec.js
@@ -1,9 +1,9 @@
-import { cloneDeep } from 'lodash';
import { mount } from '@vue/test-utils';
+import { cloneDeep } from 'lodash';
import { format } from 'timeago.js';
+import DeleteComponent from '~/environments/components/environment_delete.vue';
import EnvironmentItem from '~/environments/components/environment_item.vue';
import PinComponent from '~/environments/components/environment_pin.vue';
-import DeleteComponent from '~/environments/components/environment_delete.vue';
import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
import { environment, folder, tableData } from './mock_data';
diff --git a/spec/frontend/environments/environment_monitoring_spec.js b/spec/frontend/environments/environment_monitoring_spec.js
index bbd49c04fb6..3a53b57c3c6 100644
--- a/spec/frontend/environments/environment_monitoring_spec.js
+++ b/spec/frontend/environments/environment_monitoring_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MonitoringComponent from '~/environments/components/environment_monitoring.vue';
describe('Monitoring Component', () => {
diff --git a/spec/frontend/environments/environment_pin_spec.js b/spec/frontend/environments/environment_pin_spec.js
index f48091adb44..5cdd52294b6 100644
--- a/spec/frontend/environments/environment_pin_spec.js
+++ b/spec/frontend/environments/environment_pin_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlIcon } from '@gitlab/ui';
-import eventHub from '~/environments/event_hub';
+import { shallowMount } from '@vue/test-utils';
import PinComponent from '~/environments/components/environment_pin.vue';
+import eventHub from '~/environments/event_hub';
describe('Pin Component', () => {
let wrapper;
diff --git a/spec/frontend/environments/environment_rollback_spec.js b/spec/frontend/environments/environment_rollback_spec.js
index fb62a096c3d..b6c3d436c18 100644
--- a/spec/frontend/environments/environment_rollback_spec.js
+++ b/spec/frontend/environments/environment_rollback_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
-import eventHub from '~/environments/event_hub';
+import { shallowMount, mount } from '@vue/test-utils';
import RollbackComponent from '~/environments/components/environment_rollback.vue';
+import eventHub from '~/environments/event_hub';
describe('Rollback Component', () => {
const retryUrl = 'https://gitlab.com/retry';
diff --git a/spec/frontend/environments/environment_stop_spec.js b/spec/frontend/environments/environment_stop_spec.js
index 1865403cdc4..dff444b79f3 100644
--- a/spec/frontend/environments/environment_stop_spec.js
+++ b/spec/frontend/environments/environment_stop_spec.js
@@ -1,6 +1,6 @@
-import $ from 'jquery';
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import $ from 'jquery';
import StopComponent from '~/environments/components/environment_stop.vue';
import eventHub from '~/environments/event_hub';
diff --git a/spec/frontend/environments/environment_table_spec.js b/spec/frontend/environments/environment_table_spec.js
index daef35bcf99..863c4526bb9 100644
--- a/spec/frontend/environments/environment_table_spec.js
+++ b/spec/frontend/environments/environment_table_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
+import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
+import DeployBoard from '~/environments/components/deploy_board.vue';
import EnvironmentTable from '~/environments/components/environments_table.vue';
import eventHub from '~/environments/event_hub';
-import DeployBoard from '~/environments/components/deploy_board.vue';
-import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import { folder, deployBoardMockData } from './mock_data';
const eeOnlyProps = {
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
index d6614e2fd2b..50d84b19ce8 100644
--- a/spec/frontend/environments/environments_app_spec.js
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -1,11 +1,11 @@
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import EnableReviewAppModal from '~/environments/components/enable_review_app_modal.vue';
import Container from '~/environments/components/container.vue';
+import DeployBoard from '~/environments/components/deploy_board.vue';
import EmptyState from '~/environments/components/empty_state.vue';
+import EnableReviewAppModal from '~/environments/components/enable_review_app_modal.vue';
import EnvironmentsApp from '~/environments/components/environments_app.vue';
-import DeployBoard from '~/environments/components/deploy_board.vue';
import axios from '~/lib/utils/axios_utils';
import { environment, folder } from './mock_data';
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index 0b9e0008ed7..b469a855d23 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -1,5 +1,3 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import Vuex from 'vuex';
import {
GlButton,
GlLoadingIcon,
@@ -9,21 +7,23 @@ import {
GlAlert,
GlSprintf,
} from '@gitlab/ui';
-import { __ } from '~/locale';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import Stacktrace from '~/error_tracking/components/stacktrace.vue';
-import ErrorDetails from '~/error_tracking/components/error_details.vue';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import {
severityLevel,
severityLevelVariant,
errorStatus,
} from '~/error_tracking/components/constants';
-import Tracking from '~/tracking';
+import ErrorDetails from '~/error_tracking/components/error_details.vue';
+import Stacktrace from '~/error_tracking/components/stacktrace.vue';
import {
trackClickErrorLinkToSentryOptions,
trackErrorDetailsViewsOptions,
trackErrorStatusUpdateOptions,
} from '~/error_tracking/utils';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { __ } from '~/locale';
+import Tracking from '~/tracking';
jest.mock('~/flash');
diff --git a/spec/frontend/error_tracking/components/error_tracking_actions_spec.js b/spec/frontend/error_tracking/components/error_tracking_actions_spec.js
index 05b3d2f1dec..e21c40423c3 100644
--- a/spec/frontend/error_tracking/components/error_tracking_actions_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_actions_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
describe('Error Tracking Actions', () => {
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index da12237b1d9..c6ce236af01 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -1,12 +1,12 @@
+import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } from '@gitlab/ui';
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } from '@gitlab/ui';
import stubChildren from 'helpers/stub_children';
-import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
+import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils';
-import errorsList from './list_mock.json';
import Tracking from '~/tracking';
+import errorsList from './list_mock.json';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -298,9 +298,7 @@ describe('ErrorTrackingList', () => {
});
it('shows empty state', () => {
- expect(wrapper.find('a').attributes('href')).toBe(
- '/help/user/project/operations/error_tracking.html',
- );
+ expect(wrapper.find(GlEmptyState).isVisible()).toBe(true);
});
});
diff --git a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
index 6df25ad6819..0b43167c19b 100644
--- a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
+++ b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import StackTraceEntry from '~/error_tracking/components/stacktrace_entry.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
diff --git a/spec/frontend/error_tracking/store/actions_spec.js b/spec/frontend/error_tracking/store/actions_spec.js
index 34ad600af05..9d598344acd 100644
--- a/spec/frontend/error_tracking/store/actions_spec.js
+++ b/spec/frontend/error_tracking/store/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
import * as actions from '~/error_tracking/store/actions';
import * as types from '~/error_tracking/store/mutation_types';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/flash.js');
diff --git a/spec/frontend/error_tracking/store/details/actions_spec.js b/spec/frontend/error_tracking/store/details/actions_spec.js
index 152ecde6985..0c19dce7bad 100644
--- a/spec/frontend/error_tracking/store/details/actions_spec.js
+++ b/spec/frontend/error_tracking/store/details/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
import * as actions from '~/error_tracking/store/details/actions';
import * as types from '~/error_tracking/store/details/mutation_types';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import Poll from '~/lib/utils/poll';
let mockedAdapter;
diff --git a/spec/frontend/error_tracking/store/list/actions_spec.js b/spec/frontend/error_tracking/store/list/actions_spec.js
index a93608fe70d..39481a8576f 100644
--- a/spec/frontend/error_tracking/store/list/actions_spec.js
+++ b/spec/frontend/error_tracking/store/list/actions_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import httpStatusCodes from '~/lib/utils/http_status';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
import * as actions from '~/error_tracking/store/list/actions';
import * as types from '~/error_tracking/store/list/mutation_types';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
jest.mock('~/flash.js');
diff --git a/spec/frontend/error_tracking/store/list/mutation_spec.js b/spec/frontend/error_tracking/store/list/mutation_spec.js
index a326a6c55c0..d28d3ecc79f 100644
--- a/spec/frontend/error_tracking/store/list/mutation_spec.js
+++ b/spec/frontend/error_tracking/store/list/mutation_spec.js
@@ -1,6 +1,6 @@
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
-import mutations from '~/error_tracking/store/list/mutations';
import * as types from '~/error_tracking/store/list/mutation_types';
+import mutations from '~/error_tracking/store/list/mutations';
const ADD_RECENT_SEARCH = mutations[types.ADD_RECENT_SEARCH];
const CLEAR_RECENT_SEARCHES = mutations[types.CLEAR_RECENT_SEARCHES];
diff --git a/spec/frontend/error_tracking_settings/components/app_spec.js b/spec/frontend/error_tracking_settings/components/app_spec.js
index 5c3efa24551..e0be81b3899 100644
--- a/spec/frontend/error_tracking_settings/components/app_spec.js
+++ b/spec/frontend/error_tracking_settings/components/app_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue';
import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
diff --git a/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
index e2a8b57f555..7ebaf0c3f2a 100644
--- a/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
+++ b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlFormInput, GlButton } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
import createStore from '~/error_tracking_settings/store';
import { defaultProps } from '../mock';
diff --git a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
index d924f895da8..79518a487d4 100644
--- a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
+++ b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
@@ -1,7 +1,7 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import { pick, clone } from 'lodash';
import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
import { defaultProps, projectList, staleProject } from '../mock';
diff --git a/spec/frontend/error_tracking_settings/store/actions_spec.js b/spec/frontend/error_tracking_settings/store/actions_spec.js
index 216bf8011e8..281db7d9686 100644
--- a/spec/frontend/error_tracking_settings/store/actions_spec.js
+++ b/spec/frontend/error_tracking_settings/store/actions_spec.js
@@ -1,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/error_tracking_settings/store/actions';
import * as types from '~/error_tracking_settings/store/mutation_types';
import defaultState from '~/error_tracking_settings/store/state';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { projectList } from '../mock';
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/error_tracking_settings/store/mutation_spec.js b/spec/frontend/error_tracking_settings/store/mutation_spec.js
index fa188462c3f..78fd56904b3 100644
--- a/spec/frontend/error_tracking_settings/store/mutation_spec.js
+++ b/spec/frontend/error_tracking_settings/store/mutation_spec.js
@@ -1,7 +1,7 @@
import { TEST_HOST } from 'helpers/test_constants';
+import * as types from '~/error_tracking_settings/store/mutation_types';
import mutations from '~/error_tracking_settings/store/mutations';
import defaultState from '~/error_tracking_settings/store/state';
-import * as types from '~/error_tracking_settings/store/mutation_types';
import {
initialEmptyState,
initialPopulatedState,
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 21b894ccbef..84e71ffd204 100644
--- a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
+++ b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Component from '~/feature_flags/components/configure_feature_flags_modal.vue';
describe('Configure Feature Flags Modal', () => {
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 a754c682356..e2717b98ea9 100644
--- a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
@@ -1,13 +1,13 @@
-import Vuex from 'vuex';
+import { GlToggle, GlAlert } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { GlToggle, GlAlert } from '@gitlab/ui';
-import { TEST_HOST } from 'spec/test_constants';
+import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
-import { LEGACY_FLAG, NEW_VERSION_FLAG } from '~/feature_flags/constants';
+import { TEST_HOST } from 'spec/test_constants';
+import EditFeatureFlag from '~/feature_flags/components/edit_feature_flag.vue';
import Form from '~/feature_flags/components/form.vue';
+import { LEGACY_FLAG, NEW_VERSION_FLAG } from '~/feature_flags/constants';
import createStore from '~/feature_flags/store/edit';
-import EditFeatureFlag from '~/feature_flags/components/edit_feature_flag.vue';
import axios from '~/lib/utils/axios_utils';
const localVue = createLocalVue();
@@ -75,6 +75,8 @@ describe('Edit feature flag form', () => {
});
const findAlert = () => wrapper.find(GlAlert);
+ const findWarningGlAlert = () =>
+ wrapper.findAll(GlAlert).filter((c) => c.props('variant') === 'warning');
it('should display the iid', () => {
expect(wrapper.find('h3').text()).toContain('^5');
@@ -88,7 +90,7 @@ describe('Edit feature flag form', () => {
expect(wrapper.find(GlToggle).props('value')).toBe(true);
});
- it('should not alert users that feature flags are changing soon', () => {
+ it('should alert users the flag is read only', () => {
expect(findAlert().text()).toContain('GitLab is moving to a new way of managing feature flags');
});
@@ -96,8 +98,9 @@ describe('Edit feature flag form', () => {
it('should render the error', () => {
store.dispatch('receiveUpdateFeatureFlagError', { message: ['The name is required'] });
return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.alert-danger').exists()).toEqual(true);
- expect(wrapper.find('.alert-danger').text()).toContain('The name is required');
+ const warningGlAlert = findWarningGlAlert();
+ expect(warningGlAlert.at(1).exists()).toEqual(true);
+ expect(warningGlAlert.at(1).text()).toContain('The name is required');
});
});
});
diff --git a/spec/frontend/feature_flags/components/environments_dropdown_spec.js b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
index 042fccaa369..9194db3a182 100644
--- a/spec/frontend/feature_flags/components/environments_dropdown_spec.js
+++ b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
@@ -1,8 +1,8 @@
-import MockAdapter from 'axios-mock-adapter';
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlButton, GlSearchBoxByType } from '@gitlab/ui';
-import { TEST_HOST } from 'spec/test_constants';
+import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
diff --git a/spec/frontend/feature_flags/components/feature_flags_spec.js b/spec/frontend/feature_flags/components/feature_flags_spec.js
index 8242d667d2e..b519aab0dc4 100644
--- a/spec/frontend/feature_flags/components/feature_flags_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_spec.js
@@ -1,18 +1,18 @@
+import { GlAlert, GlEmptyState, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
import MockAdapter from 'axios-mock-adapter';
-import { GlAlert, GlEmptyState, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
+import Vuex from 'vuex';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
-import createStore from '~/feature_flags/store/index';
-import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue';
+import ConfigureFeatureFlagsModal from '~/feature_flags/components/configure_feature_flags_modal.vue';
import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue';
+import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue';
import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue';
import UserListsTable from '~/feature_flags/components/user_lists_table.vue';
-import ConfigureFeatureFlagsModal from '~/feature_flags/components/configure_feature_flags_modal.vue';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '~/feature_flags/constants';
-import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import createStore from '~/feature_flags/store/index';
import axios from '~/lib/utils/axios_utils';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import { getRequestData, userList } from '../mock_data';
const localVue = createLocalVue();
diff --git a/spec/frontend/feature_flags/components/feature_flags_tab_spec.js b/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
index 23cc7045d1f..c2170e8a768 100644
--- a/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlAlert, GlBadge, GlEmptyState, GlLink, GlLoadingIcon, GlTabs } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue';
const DEFAULT_PROPS = {
diff --git a/spec/frontend/feature_flags/components/feature_flags_table_spec.js b/spec/frontend/feature_flags/components/feature_flags_table_spec.js
index 8881cfae88d..8f4d39d4a11 100644
--- a/spec/frontend/feature_flags/components/feature_flags_table_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_table_spec.js
@@ -1,7 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlToggle, GlBadge } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import { mockTracking } from 'helpers/tracking_helper';
+import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
@@ -11,7 +12,6 @@ import {
LEGACY_FLAG,
DEFAULT_PERCENT_ROLLOUT,
} from '~/feature_flags/constants';
-import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue';
const getDefaultProps = () => ({
featureFlags: [
diff --git a/spec/frontend/feature_flags/components/form_spec.js b/spec/frontend/feature_flags/components/form_spec.js
index 3a057aedde9..a05e23a4250 100644
--- a/spec/frontend/feature_flags/components/form_spec.js
+++ b/spec/frontend/feature_flags/components/form_spec.js
@@ -1,9 +1,10 @@
-import { uniqueId } from 'lodash';
+import { GlFormTextarea, GlFormCheckbox, GlButton, GlToggle } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlFormTextarea, GlFormCheckbox, GlButton } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Api from '~/api';
-import Form from '~/feature_flags/components/form.vue';
import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue';
+import Form from '~/feature_flags/components/form.vue';
import Strategy from '~/feature_flags/components/strategy.vue';
import {
ROLLOUT_STRATEGY_ALL_USERS,
@@ -14,7 +15,6 @@ import {
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
-import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import { featureFlag, userList, allUsersStrategy } from '../mock_data';
jest.mock('~/api.js');
@@ -35,14 +35,19 @@ describe('feature flag form', () => {
},
};
+ const findAddNewScopeRow = () => wrapper.findByTestId('add-new-scope');
+ const findGlToggle = () => wrapper.find(GlToggle);
+
const factory = (props = {}, provide = {}) => {
- wrapper = shallowMount(Form, {
- propsData: { ...requiredProps, ...props },
- provide: {
- ...requiredInjections,
- ...provide,
- },
- });
+ wrapper = extendedWrapper(
+ shallowMount(Form, {
+ propsData: { ...requiredProps, ...props },
+ provide: {
+ ...requiredInjections,
+ ...provide,
+ },
+ }),
+ );
};
beforeEach(() => {
@@ -102,13 +107,13 @@ describe('feature flag form', () => {
});
it('should render scopes table with a new row ', () => {
- expect(wrapper.find('.js-add-new-scope').exists()).toBe(true);
+ expect(findAddNewScopeRow().exists()).toBe(true);
});
describe('status toggle', () => {
describe('without filled text input', () => {
it('should add a new scope with the text value empty and the status', () => {
- wrapper.find(ToggleButton).vm.$emit('change', true);
+ findGlToggle().vm.$emit('change', true);
expect(wrapper.vm.formScopes).toHaveLength(1);
expect(wrapper.vm.formScopes[0].active).toEqual(true);
@@ -121,7 +126,7 @@ describe('feature flag form', () => {
it('should be disabled if the feature flag is not active', (done) => {
wrapper.setProps({ active: false });
wrapper.vm.$nextTick(() => {
- expect(wrapper.find(ToggleButton).props('disabledInput')).toBe(true);
+ expect(findGlToggle().props('disabled')).toBe(true);
done();
});
});
@@ -166,11 +171,11 @@ describe('feature flag form', () => {
describe('scopes', () => {
it('should be possible to remove a scope', () => {
- expect(wrapper.find('.js-feature-flag-delete').exists()).toEqual(true);
+ expect(wrapper.findByTestId('feature-flag-delete').exists()).toEqual(true);
});
it('renders empty row to add a new scope', () => {
- expect(wrapper.find('.js-add-new-scope').exists()).toEqual(true);
+ expect(findAddNewScopeRow().exists()).toEqual(true);
});
it('renders the user id checkbox', () => {
@@ -186,7 +191,7 @@ describe('feature flag form', () => {
describe('update scope', () => {
describe('on click on toggle', () => {
it('should update the scope', () => {
- wrapper.find(ToggleButton).vm.$emit('change', false);
+ findGlToggle().vm.$emit('change', false);
expect(wrapper.vm.formScopes[0].active).toBe(false);
});
@@ -195,7 +200,7 @@ describe('feature flag form', () => {
wrapper.setProps({ active: false });
wrapper.vm.$nextTick(() => {
- expect(wrapper.find(ToggleButton).props('disabledInput')).toBe(true);
+ expect(findGlToggle().props('disabled')).toBe(true);
done();
});
});
@@ -294,7 +299,7 @@ describe('feature flag form', () => {
const row = wrapper.findAll('.gl-responsive-table-row').at(2);
expect(row.find(EnvironmentsDropdown).vm.disabled).toBe(true);
- expect(row.find(ToggleButton).vm.disabledInput).toBe(true);
+ expect(row.find(GlToggle).props('disabled')).toBe(true);
expect(row.find('.js-delete-scope').exists()).toBe(false);
});
});
@@ -347,10 +352,10 @@ describe('feature flag form', () => {
return wrapper.vm.$nextTick();
})
.then(() => {
- wrapper.find('.js-add-new-scope').find(ToggleButton).vm.$emit('change', true);
+ findAddNewScopeRow().find(GlToggle).vm.$emit('change', true);
})
.then(() => {
- wrapper.find(ToggleButton).vm.$emit('change', true);
+ findGlToggle().vm.$emit('change', true);
return wrapper.vm.$nextTick();
})
diff --git a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js
index ad58ceaf5f8..6342ac0bda7 100644
--- a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js
+++ b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js
@@ -1,6 +1,6 @@
+import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import NewEnvironmentsDropdown from '~/feature_flags/components/new_environments_dropdown.vue';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
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 e317ac4b092..e209c14d8c7 100644
--- a/spec/frontend/feature_flags/components/new_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/new_feature_flag_spec.js
@@ -1,11 +1,11 @@
+import { GlAlert } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlAlert } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import Form from '~/feature_flags/components/form.vue';
-import createStore from '~/feature_flags/store/new';
import NewFeatureFlag from '~/feature_flags/components/new_feature_flag.vue';
import { ROLLOUT_STRATEGY_ALL_USERS, DEFAULT_PERCENT_ROLLOUT } from '~/feature_flags/constants';
+import createStore from '~/feature_flags/store/new';
import { allUsersStrategy } from '../mock_data';
const userCalloutId = 'feature_flags_new_version';
@@ -41,6 +41,9 @@ describe('New feature flag form', () => {
});
};
+ const findWarningGlAlert = () =>
+ wrapper.findAll(GlAlert).filter((c) => c.props('variant') === 'warning');
+
beforeEach(() => {
factory();
});
@@ -53,8 +56,9 @@ describe('New feature flag form', () => {
it('should render the error', () => {
store.dispatch('receiveCreateFeatureFlagError', { message: ['The name is required'] });
return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.alert').exists()).toEqual(true);
- expect(wrapper.find('.alert').text()).toContain('The name is required');
+ const warningGlAlert = findWarningGlAlert();
+ expect(warningGlAlert.at(0).exists()).toBe(true);
+ expect(warningGlAlert.at(0).text()).toContain('The name is required');
});
});
});
@@ -81,10 +85,6 @@ describe('New feature flag form', () => {
expect(wrapper.find(Form).props('scopes')).toContainEqual(defaultScope);
});
- it('should not alert users that feature flags are changing soon', () => {
- expect(wrapper.find(GlAlert).exists()).toBe(false);
- });
-
it('has an all users strategy by default', () => {
const strategies = wrapper.find(Form).props('strategies');
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 725f53d4409..02216370b79 100644
--- a/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlFormInput, GlFormSelect } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import FlexibleRollout from '~/feature_flags/components/strategies/flexible_rollout.vue';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
import { PERCENT_ROLLOUT_GROUP_ID } from '~/feature_flags/constants';
diff --git a/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js b/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js
index 1c85eadc678..6188672b23b 100644
--- a/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js
@@ -1,9 +1,9 @@
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
-import createStore from '~/feature_flags/store/new';
import GitlabUserList from '~/feature_flags/components/strategies/gitlab_user_list.vue';
+import createStore from '~/feature_flags/store/new';
import { userListStrategy, userList } from '../../mock_data';
jest.mock('~/api');
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 a0ffdb1fca0..33696064d55 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
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
describe('~/feature_flags/strategies/parameter_form_group.vue', () => {
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 696b3b2e4c9..442f7faf161 100644
--- a/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
@@ -1,7 +1,7 @@
-import { mount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
-import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
+import { mount } from '@vue/test-utils';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
+import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
import { PERCENT_ROLLOUT_GROUP_ID } from '~/feature_flags/constants';
import { percentRolloutStrategy } from '../../mock_data';
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 460df6ef2ec..745fbca00fe 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
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlFormTextarea } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import UsersWithId from '~/feature_flags/components/strategies/users_with_id.vue';
import { usersWithIdStrategy } from '../../mock_data';
diff --git a/spec/frontend/feature_flags/components/strategy_parameters_spec.js b/spec/frontend/feature_flags/components/strategy_parameters_spec.js
index 82811c05195..979ca255b08 100644
--- a/spec/frontend/feature_flags/components/strategy_parameters_spec.js
+++ b/spec/frontend/feature_flags/components/strategy_parameters_spec.js
@@ -1,16 +1,16 @@
import { shallowMount } from '@vue/test-utils';
import { last } from 'lodash';
+import Default from '~/feature_flags/components/strategies/default.vue';
+import GitlabUserList from '~/feature_flags/components/strategies/gitlab_user_list.vue';
+import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
+import UsersWithId from '~/feature_flags/components/strategies/users_with_id.vue';
+import StrategyParameters from '~/feature_flags/components/strategy_parameters.vue';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
} from '~/feature_flags/constants';
-import Default from '~/feature_flags/components/strategies/default.vue';
-import GitlabUserList from '~/feature_flags/components/strategies/gitlab_user_list.vue';
-import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
-import UsersWithId from '~/feature_flags/components/strategies/users_with_id.vue';
-import StrategyParameters from '~/feature_flags/components/strategy_parameters.vue';
import { allUsersStrategy } from '../mock_data';
const DEFAULT_PROPS = {
diff --git a/spec/frontend/feature_flags/components/strategy_spec.js b/spec/frontend/feature_flags/components/strategy_spec.js
index 67cf70c37e2..4fdf436bfc4 100644
--- a/spec/frontend/feature_flags/components/strategy_spec.js
+++ b/spec/frontend/feature_flags/components/strategy_spec.js
@@ -1,9 +1,11 @@
+import { GlAlert, GlFormSelect, GlLink, GlToken, GlButton } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
import { last } from 'lodash';
-import { GlAlert, GlFormSelect, GlLink, GlToken, GlButton } from '@gitlab/ui';
+import Vuex from 'vuex';
import Api from '~/api';
-import createStore from '~/feature_flags/store/new';
+import NewEnvironmentsDropdown from '~/feature_flags/components/new_environments_dropdown.vue';
+import Strategy from '~/feature_flags/components/strategy.vue';
+import StrategyParameters from '~/feature_flags/components/strategy_parameters.vue';
import {
PERCENT_ROLLOUT_GROUP_ID,
ROLLOUT_STRATEGY_ALL_USERS,
@@ -12,9 +14,7 @@ import {
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
} from '~/feature_flags/constants';
-import Strategy from '~/feature_flags/components/strategy.vue';
-import NewEnvironmentsDropdown from '~/feature_flags/components/new_environments_dropdown.vue';
-import StrategyParameters from '~/feature_flags/components/strategy_parameters.vue';
+import createStore from '~/feature_flags/store/new';
import { userList } from '../mock_data';
diff --git a/spec/frontend/feature_flags/components/user_lists_table_spec.js b/spec/frontend/feature_flags/components/user_lists_table_spec.js
index 974f63ba934..1b04ecee146 100644
--- a/spec/frontend/feature_flags/components/user_lists_table_spec.js
+++ b/spec/frontend/feature_flags/components/user_lists_table_spec.js
@@ -1,6 +1,6 @@
+import { GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import * as timeago from 'timeago.js';
-import { GlModal } from '@gitlab/ui';
import UserListsTable from '~/feature_flags/components/user_lists_table.vue';
import { userList } from '../mock_data';
diff --git a/spec/frontend/feature_flags/store/edit/actions_spec.js b/spec/frontend/feature_flags/store/edit/actions_spec.js
index 20cec5daac0..afcac53468c 100644
--- a/spec/frontend/feature_flags/store/edit/actions_spec.js
+++ b/spec/frontend/feature_flags/store/edit/actions_spec.js
@@ -2,6 +2,11 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import {
+ NEW_VERSION_FLAG,
+ LEGACY_FLAG,
+ ROLLOUT_STRATEGY_ALL_USERS,
+} from '~/feature_flags/constants';
+import {
updateFeatureFlag,
requestUpdateFeatureFlag,
receiveUpdateFeatureFlagSuccess,
@@ -12,14 +17,9 @@ import {
receiveFeatureFlagError,
toggleActive,
} from '~/feature_flags/store/edit/actions';
+import * as types from '~/feature_flags/store/edit/mutation_types';
import state from '~/feature_flags/store/edit/state';
import { mapStrategiesToRails, mapFromScopesViewModel } from '~/feature_flags/store/helpers';
-import {
- NEW_VERSION_FLAG,
- LEGACY_FLAG,
- ROLLOUT_STRATEGY_ALL_USERS,
-} from '~/feature_flags/constants';
-import * as types from '~/feature_flags/store/edit/mutation_types';
import axios from '~/lib/utils/axios_utils';
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/feature_flags/store/edit/mutations_spec.js b/spec/frontend/feature_flags/store/edit/mutations_spec.js
index 1d817fb8004..4b43f8b82df 100644
--- a/spec/frontend/feature_flags/store/edit/mutations_spec.js
+++ b/spec/frontend/feature_flags/store/edit/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/feature_flags/store/edit/state';
-import mutations from '~/feature_flags/store/edit/mutations';
import * as types from '~/feature_flags/store/edit/mutation_types';
+import mutations from '~/feature_flags/store/edit/mutations';
+import state from '~/feature_flags/store/edit/state';
describe('Feature flags Edit Module Mutations', () => {
let stateCopy;
diff --git a/spec/frontend/feature_flags/store/gitlab_user_lists/actions_spec.js b/spec/frontend/feature_flags/store/gitlab_user_lists/actions_spec.js
index aba578cca59..b4887d23e4b 100644
--- a/spec/frontend/feature_flags/store/gitlab_user_lists/actions_spec.js
+++ b/spec/frontend/feature_flags/store/gitlab_user_lists/actions_spec.js
@@ -1,8 +1,8 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import createState from '~/feature_flags/store/gitlab_user_list/state';
import { fetchUserLists, setFilter } from '~/feature_flags/store/gitlab_user_list/actions';
import * as types from '~/feature_flags/store/gitlab_user_list/mutation_types';
+import createState from '~/feature_flags/store/gitlab_user_list/state';
import { userList } from '../../mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/feature_flags/store/gitlab_user_lists/getters_spec.js b/spec/frontend/feature_flags/store/gitlab_user_lists/getters_spec.js
index e267cd59f50..1f02cbb44a1 100644
--- a/spec/frontend/feature_flags/store/gitlab_user_lists/getters_spec.js
+++ b/spec/frontend/feature_flags/store/gitlab_user_lists/getters_spec.js
@@ -4,8 +4,8 @@ import {
isLoading,
hasError,
} from '~/feature_flags/store/gitlab_user_list/getters';
-import statuses from '~/feature_flags/store/gitlab_user_list/status';
import createState from '~/feature_flags/store/gitlab_user_list/state';
+import statuses from '~/feature_flags/store/gitlab_user_list/status';
import { userList } from '../../mock_data';
describe('~/feature_flags/store/gitlab_user_list/getters', () => {
diff --git a/spec/frontend/feature_flags/store/gitlab_user_lists/mutations_spec.js b/spec/frontend/feature_flags/store/gitlab_user_lists/mutations_spec.js
index 88d4554a227..46233c43b07 100644
--- a/spec/frontend/feature_flags/store/gitlab_user_lists/mutations_spec.js
+++ b/spec/frontend/feature_flags/store/gitlab_user_lists/mutations_spec.js
@@ -1,7 +1,7 @@
-import statuses from '~/feature_flags/store/gitlab_user_list/status';
-import createState from '~/feature_flags/store/gitlab_user_list/state';
import * as types from '~/feature_flags/store/gitlab_user_list/mutation_types';
import mutations from '~/feature_flags/store/gitlab_user_list/mutations';
+import createState from '~/feature_flags/store/gitlab_user_list/state';
+import statuses from '~/feature_flags/store/gitlab_user_list/status';
import { userList } from '../../mock_data';
describe('~/feature_flags/store/gitlab_user_list/mutations', () => {
diff --git a/spec/frontend/feature_flags/store/helpers_spec.js b/spec/frontend/feature_flags/store/helpers_spec.js
index 301b1d09fcc..711e2a1286e 100644
--- a/spec/frontend/feature_flags/store/helpers_spec.js
+++ b/spec/frontend/feature_flags/store/helpers_spec.js
@@ -1,12 +1,5 @@
import { uniqueId } from 'lodash';
import {
- mapToScopesViewModel,
- mapFromScopesViewModel,
- createNewEnvironmentScope,
- mapStrategiesToViewModel,
- mapStrategiesToRails,
-} from '~/feature_flags/store/helpers';
-import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
@@ -16,6 +9,13 @@ import {
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
+import {
+ mapToScopesViewModel,
+ mapFromScopesViewModel,
+ createNewEnvironmentScope,
+ mapStrategiesToViewModel,
+ mapStrategiesToRails,
+} from '~/feature_flags/store/helpers';
describe('feature flags helpers spec', () => {
describe('mapToScopesViewModel', () => {
diff --git a/spec/frontend/feature_flags/store/index/actions_spec.js b/spec/frontend/feature_flags/store/index/actions_spec.js
index 82e16958d33..a7ab2e92cb2 100644
--- a/spec/frontend/feature_flags/store/index/actions_spec.js
+++ b/spec/frontend/feature_flags/store/index/actions_spec.js
@@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
+import { mapToScopesViewModel } from '~/feature_flags/store/helpers';
import {
requestFeatureFlags,
receiveFeatureFlagsSuccess,
@@ -24,9 +25,8 @@ import {
receiveDeleteUserListError,
clearAlert,
} from '~/feature_flags/store/index/actions';
-import { mapToScopesViewModel } from '~/feature_flags/store/helpers';
-import state from '~/feature_flags/store/index/state';
import * as types from '~/feature_flags/store/index/mutation_types';
+import state from '~/feature_flags/store/index/state';
import axios from '~/lib/utils/axios_utils';
import { getRequestData, rotateData, featureFlag, userList } from '../../mock_data';
diff --git a/spec/frontend/feature_flags/store/index/mutations_spec.js b/spec/frontend/feature_flags/store/index/mutations_spec.js
index eec56800a8d..08b5868d1b4 100644
--- a/spec/frontend/feature_flags/store/index/mutations_spec.js
+++ b/spec/frontend/feature_flags/store/index/mutations_spec.js
@@ -1,7 +1,7 @@
-import state from '~/feature_flags/store/index/state';
-import mutations from '~/feature_flags/store/index/mutations';
-import * as types from '~/feature_flags/store/index/mutation_types';
import { mapToScopesViewModel } from '~/feature_flags/store/helpers';
+import * as types from '~/feature_flags/store/index/mutation_types';
+import mutations from '~/feature_flags/store/index/mutations';
+import state from '~/feature_flags/store/index/state';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { getRequestData, rotateData, featureFlag, userList } from '../../mock_data';
diff --git a/spec/frontend/feature_flags/store/new/actions_spec.js b/spec/frontend/feature_flags/store/new/actions_spec.js
index 9168f11fdfb..00dfb982ded 100644
--- a/spec/frontend/feature_flags/store/new/actions_spec.js
+++ b/spec/frontend/feature_flags/store/new/actions_spec.js
@@ -2,20 +2,20 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import {
- createFeatureFlag,
- requestCreateFeatureFlag,
- receiveCreateFeatureFlagSuccess,
- receiveCreateFeatureFlagError,
-} from '~/feature_flags/store/new/actions';
-import state from '~/feature_flags/store/new/state';
-import * as types from '~/feature_flags/store/new/mutation_types';
-import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
import { mapFromScopesViewModel, mapStrategiesToRails } from '~/feature_flags/store/helpers';
+import {
+ createFeatureFlag,
+ requestCreateFeatureFlag,
+ receiveCreateFeatureFlagSuccess,
+ receiveCreateFeatureFlagError,
+} from '~/feature_flags/store/new/actions';
+import * as types from '~/feature_flags/store/new/mutation_types';
+import state from '~/feature_flags/store/new/state';
import axios from '~/lib/utils/axios_utils';
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/feature_flags/store/new/mutations_spec.js b/spec/frontend/feature_flags/store/new/mutations_spec.js
index e8609a6d116..c97e62247bb 100644
--- a/spec/frontend/feature_flags/store/new/mutations_spec.js
+++ b/spec/frontend/feature_flags/store/new/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/feature_flags/store/new/state';
-import mutations from '~/feature_flags/store/new/mutations';
import * as types from '~/feature_flags/store/new/mutation_types';
+import mutations from '~/feature_flags/store/new/mutations';
+import state from '~/feature_flags/store/new/state';
describe('Feature flags New Module Mutations', () => {
let stateCopy;
diff --git a/spec/frontend/feature_highlight/feature_highlight_helper_spec.js b/spec/frontend/feature_highlight/feature_highlight_helper_spec.js
index beae5041156..1b5bffc1f9b 100644
--- a/spec/frontend/feature_highlight/feature_highlight_helper_spec.js
+++ b/spec/frontend/feature_highlight/feature_highlight_helper_spec.js
@@ -1,62 +1,40 @@
-import $ from 'jquery';
+import MockAdapter from 'axios-mock-adapter';
+import { dismiss } from '~/feature_highlight/feature_highlight_helper';
+import { deprecatedCreateFlash as Flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import { getSelector, dismiss, inserted } from '~/feature_highlight/feature_highlight_helper';
-import { togglePopover } from '~/shared/popover';
+import httpStatusCodes from '~/lib/utils/http_status';
-describe('feature highlight helper', () => {
- describe('getSelector', () => {
- it('returns js-feature-highlight selector', () => {
- const highlightId = 'highlightId';
-
- expect(getSelector(highlightId)).toEqual(
- `.js-feature-highlight[data-highlight=${highlightId}]`,
- );
- });
- });
+jest.mock('~/flash');
+describe('feature highlight helper', () => {
describe('dismiss', () => {
- const context = {
- hide: () => {},
- attr: () => '/-/callouts/dismiss',
- };
+ let mockAxios;
+ const endpoint = '/-/callouts/dismiss';
+ const highlightId = '123';
+ const { CREATED, INTERNAL_SERVER_ERROR } = httpStatusCodes;
beforeEach(() => {
- jest.spyOn(axios, 'post').mockResolvedValue();
- jest.spyOn(togglePopover, 'call').mockImplementation(() => {});
- jest.spyOn(context, 'hide').mockImplementation(() => {});
- dismiss.call(context);
+ mockAxios = new MockAdapter(axios);
});
- it('calls persistent dismissal endpoint', () => {
- expect(axios.post).toHaveBeenCalledWith(
- '/-/callouts/dismiss',
- expect.objectContaining({ feature_name: undefined }),
- );
+ afterEach(() => {
+ mockAxios.reset();
});
- it('calls hide popover', () => {
- expect(togglePopover.call).toHaveBeenCalledWith(context, false);
- });
+ it('calls persistent dismissal endpoint with highlightId', async () => {
+ mockAxios.onPost(endpoint, { feature_name: highlightId }).replyOnce(CREATED);
- it('calls hide', () => {
- expect(context.hide).toHaveBeenCalled();
+ await expect(dismiss(endpoint, highlightId)).resolves.toEqual(expect.anything());
});
- });
- describe('inserted', () => {
- it('registers click event callback', (done) => {
- const context = {
- getAttribute: () => 'popoverId',
- dataset: {
- highlight: 'some-feature',
- },
- };
-
- jest.spyOn($.fn, 'on').mockImplementation((event) => {
- expect(event).toEqual('click');
- done();
- });
- inserted.call(context);
+ it('triggers flash when dismiss request fails', async () => {
+ mockAxios.onPost(endpoint, { feature_name: highlightId }).replyOnce(INTERNAL_SERVER_ERROR);
+
+ await dismiss(endpoint, highlightId);
+
+ expect(Flash).toHaveBeenCalledWith(
+ 'An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.',
+ );
});
});
});
diff --git a/spec/frontend/feature_highlight/feature_highlight_options_spec.js b/spec/frontend/feature_highlight/feature_highlight_options_spec.js
deleted file mode 100644
index f82f984cb7f..00000000000
--- a/spec/frontend/feature_highlight/feature_highlight_options_spec.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import domContentLoaded from '~/feature_highlight/feature_highlight_options';
-
-describe('feature highlight options', () => {
- describe('domContentLoaded', () => {
- it.each`
- breakPoint | shouldCall
- ${'xs'} | ${false}
- ${'sm'} | ${false}
- ${'md'} | ${false}
- ${'lg'} | ${false}
- ${'xl'} | ${true}
- `(
- 'when breakpoint is $breakPoint should call highlightFeatures is $shouldCall',
- ({ breakPoint, shouldCall }) => {
- jest.spyOn(bp, 'getBreakpointSize').mockReturnValue(breakPoint);
-
- expect(domContentLoaded()).toBe(shouldCall);
- },
- );
- });
-});
diff --git a/spec/frontend/feature_highlight/feature_highlight_popover_spec.js b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
new file mode 100644
index 00000000000..1d558366ce8
--- /dev/null
+++ b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
@@ -0,0 +1,80 @@
+import { GlPopover, GlLink, GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { POPOVER_TARGET_ID } from '~/feature_highlight/constants';
+import { dismiss } from '~/feature_highlight/feature_highlight_helper';
+import FeatureHighlightPopover from '~/feature_highlight/feature_highlight_popover.vue';
+
+jest.mock('~/feature_highlight/feature_highlight_helper');
+
+describe('feature_highlight/feature_highlight_popover', () => {
+ let wrapper;
+ const props = {
+ autoDevopsHelpPath: '/help/autodevops',
+ highlightId: '123',
+ dismissEndpoint: '/api/dismiss',
+ };
+
+ const buildWrapper = (propsData = props) => {
+ wrapper = mount(FeatureHighlightPopover, {
+ propsData,
+ });
+ };
+ const findPopoverTarget = () => wrapper.find(`#${POPOVER_TARGET_ID}`);
+ const findPopover = () => wrapper.findComponent(GlPopover);
+ const findAutoDevopsHelpLink = () => wrapper.findComponent(GlLink);
+ const findDismissButton = () => wrapper.findComponent(GlButton);
+
+ beforeEach(() => {
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders popover target', () => {
+ expect(findPopoverTarget().exists()).toBe(true);
+ });
+
+ it('renders popover', () => {
+ expect(findPopover().props()).toMatchObject({
+ target: POPOVER_TARGET_ID,
+ cssClasses: ['feature-highlight-popover'],
+ triggers: 'hover',
+ container: 'body',
+ placement: 'right',
+ boundary: 'viewport',
+ });
+ });
+
+ it('renders link that points to the autodevops help page', () => {
+ expect(findAutoDevopsHelpLink().attributes().href).toBe(props.autoDevopsHelpPath);
+ expect(findAutoDevopsHelpLink().text()).toBe('Auto DevOps');
+ });
+
+ it('renders dismiss button', () => {
+ expect(findDismissButton().props()).toMatchObject({
+ size: 'small',
+ icon: 'thumb-up',
+ variant: 'confirm',
+ });
+ });
+
+ it('dismisses popover when dismiss button is clicked', async () => {
+ await findDismissButton().trigger('click');
+
+ expect(findPopover().emitted('close')).toHaveLength(1);
+ expect(dismiss).toHaveBeenCalledWith(props.dismissEndpoint, props.highlightId);
+ });
+
+ describe('when popover is dismissed and hidden', () => {
+ it('hides the popover target', async () => {
+ await findDismissButton().trigger('click');
+ findPopover().vm.$emit('hidden');
+ await wrapper.vm.$nextTick();
+
+ expect(findPopoverTarget().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/feature_highlight/feature_highlight_spec.js b/spec/frontend/feature_highlight/feature_highlight_spec.js
deleted file mode 100644
index 79c4050c8c4..00000000000
--- a/spec/frontend/feature_highlight/feature_highlight_spec.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import $ from 'jquery';
-import MockAdapter from 'axios-mock-adapter';
-import * as featureHighlight from '~/feature_highlight/feature_highlight';
-import * as popover from '~/shared/popover';
-import axios from '~/lib/utils/axios_utils';
-
-jest.mock('~/shared/popover');
-
-describe('feature highlight', () => {
- beforeEach(() => {
- setFixtures(`
- <div>
- <div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" data-dismiss-endpoint="/test" disabled>
- Trigger
- </div>
- </div>
- <div class="feature-highlight-popover-content">
- Content
- <div class="dismiss-feature-highlight">
- Dismiss
- </div>
- </div>
- `);
- });
-
- describe('setupFeatureHighlightPopover', () => {
- let mock;
- const selector = '.js-feature-highlight[data-highlight=test]';
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet('/test').reply(200);
- jest.spyOn(window, 'addEventListener').mockImplementation(() => {});
- featureHighlight.setupFeatureHighlightPopover('test', 0);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('setup popover content', () => {
- const $popoverContent = $('.feature-highlight-popover-content');
- const outerHTML = $popoverContent.prop('outerHTML');
-
- expect($(selector).data('content')).toEqual(outerHTML);
- });
-
- it('setup mouseenter', () => {
- $(selector).trigger('mouseenter');
-
- expect(popover.mouseenter).toHaveBeenCalledWith(expect.any(Object));
- });
-
- it('setup debounced mouseleave', () => {
- $(selector).trigger('mouseleave');
-
- expect(popover.debouncedMouseleave).toHaveBeenCalled();
- });
-
- it('setup show.bs.popover', () => {
- $(selector).trigger('show.bs.popover');
-
- expect(window.addEventListener).toHaveBeenCalledWith('scroll', expect.any(Function), {
- once: true,
- });
- });
-
- it('removes disabled attribute', () => {
- expect($('.js-feature-highlight').is(':disabled')).toEqual(false);
- });
- });
-
- describe('findHighestPriorityFeature', () => {
- beforeEach(() => {
- setFixtures(`
- <div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-high-priority" data-highlight-priority="20" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-low-priority" data-highlight-priority="0" disabled></div>
- `);
- });
-
- it('should pick the highest priority feature highlight', () => {
- setFixtures(`
- <div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-high-priority" data-highlight-priority="20" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-low-priority" data-highlight-priority="0" disabled></div>
- `);
-
- expect($('.js-feature-highlight').length).toBeGreaterThan(1);
- expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
- });
-
- it('should work when no priority is set', () => {
- setFixtures(`
- <div class="js-feature-highlight" data-highlight="test" disabled></div>
- `);
-
- expect(featureHighlight.findHighestPriorityFeature()).toEqual('test');
- });
-
- it('should pick the highest priority feature highlight when some have no priority set', () => {
- setFixtures(`
- <div class="js-feature-highlight" data-highlight="test-no-priority1" disabled></div>
- <div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-no-priority2" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-high-priority" data-highlight-priority="20" disabled></div>
- <div class="js-feature-highlight" data-highlight="test-low-priority" data-highlight-priority="0" disabled></div>
- `);
-
- expect($('.js-feature-highlight').length).toBeGreaterThan(1);
- expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
- });
- });
-
- describe('highlightFeatures', () => {
- it('calls setupFeatureHighlightPopover', () => {
- expect(featureHighlight.highlightFeatures()).toEqual('test');
- });
- });
-});
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 f1cff02261d..897ad5ee2bf 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
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import eventHub from '~/filtered_search/event_hub';
import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content.vue';
+import eventHub from '~/filtered_search/event_hub';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
describe('Recent Searches Dropdown Content', () => {
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index c1c09ea5d3e..0e2d2ee6c09 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -1,4 +1,7 @@
import DropdownUtils from '~/filtered_search/dropdown_utils';
+// TODO: Moving this line up throws an error about `FilteredSearchDropdown`
+// being undefined in test. See gitlab-org/gitlab#321476 for more info.
+// eslint-disable-next-line import/order
import DropdownUser from '~/filtered_search/dropdown_user';
import FilteredSearchTokenizer from '~/filtered_search/filtered_search_tokenizer';
import IssuableFilteredTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index def0aa14349..465e1ee1ef1 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -1,16 +1,16 @@
import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
-import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
-import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
-import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import DropdownUtils from '~/filtered_search/dropdown_utils';
-import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
+import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+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 * as commonUtils from '~/lib/utils/common_utils';
import { BACKSPACE_KEY_CODE, DELETE_KEY_CODE } from '~/lib/utils/keycodes';
import { visitUrl } from '~/lib/utils/url_utility';
-import * as commonUtils from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
diff --git a/spec/frontend/filtered_search/filtered_search_tokenizer_spec.js b/spec/frontend/filtered_search/filtered_search_tokenizer_spec.js
index dec03e5ab93..b6a95eb55c7 100644
--- a/spec/frontend/filtered_search/filtered_search_tokenizer_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_tokenizer_spec.js
@@ -1,5 +1,5 @@
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import FilteredSearchTokenizer from '~/filtered_search/filtered_search_tokenizer';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
describe('Filtered Search Tokenizer', () => {
const allowedKeys = IssuableFilteredSearchTokenKeys.getKeys();
diff --git a/spec/frontend/filtered_search/recent_searches_root_spec.js b/spec/frontend/filtered_search/recent_searches_root_spec.js
index 6bb9e68d591..fa3267c98a1 100644
--- a/spec/frontend/filtered_search/recent_searches_root_spec.js
+++ b/spec/frontend/filtered_search/recent_searches_root_spec.js
@@ -1,32 +1,51 @@
-import Vue from 'vue';
+import { setHTMLFixture } from 'helpers/fixtures';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
-jest.mock('vue');
+const containerId = 'test-container';
+const dropdownElementId = 'test-dropdown-element';
describe('RecentSearchesRoot', () => {
describe('render', () => {
- let recentSearchesRoot;
- let data;
- let template;
+ let recentSearchesRootMockInstance;
+ let vm;
+ let containerEl;
beforeEach(() => {
- recentSearchesRoot = {
+ setHTMLFixture(`
+ <div id="${containerId}">
+ <div id="${dropdownElementId}"></div>
+ </div>
+ `);
+
+ containerEl = document.getElementById(containerId);
+
+ recentSearchesRootMockInstance = {
store: {
- state: 'state',
+ state: {
+ recentSearches: ['foo', 'bar', 'qux'],
+ isLocalStorageAvailable: true,
+ allowedKeys: ['test'],
+ },
},
+ wrapperElement: document.getElementById(dropdownElementId),
};
- Vue.mockImplementation((options) => {
- ({ data, template } = options);
- });
+ RecentSearchesRoot.prototype.render.call(recentSearchesRootMockInstance);
+ vm = recentSearchesRootMockInstance.vm;
- RecentSearchesRoot.prototype.render.call(recentSearchesRoot);
+ return vm.$nextTick();
});
- it('should instantiate Vue', () => {
- expect(Vue).toHaveBeenCalled();
- expect(data()).toBe(recentSearchesRoot.store.state);
- expect(template).toContain(':is-local-storage-available="isLocalStorageAvailable"');
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render the recent searches', () => {
+ const { recentSearches } = recentSearchesRootMockInstance.store.state;
+
+ recentSearches.forEach((recentSearch) => {
+ expect(containerEl.textContent).toContain(recentSearch);
+ });
});
});
});
diff --git a/spec/frontend/filtered_search/visual_token_value_spec.js b/spec/frontend/filtered_search/visual_token_value_spec.js
index 2a76c4a27df..a2082271efe 100644
--- a/spec/frontend/filtered_search/visual_token_value_spec.js
+++ b/spec/frontend/filtered_search/visual_token_value_spec.js
@@ -1,10 +1,10 @@
import { escape } from 'lodash';
-import { TEST_HOST } from 'helpers/test_constants';
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 AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
-import DropdownUtils from '~/filtered_search//dropdown_utils';
describe('Filtered Search Visual Tokens', () => {
const findElements = (tokenElement) => {
diff --git a/spec/frontend/fixtures/api_merge_requests.rb b/spec/frontend/fixtures/api_merge_requests.rb
index f3280e216ff..7117c9a1c7a 100644
--- a/spec/frontend/fixtures/api_merge_requests.rb
+++ b/spec/frontend/fixtures/api_merge_requests.rb
@@ -6,9 +6,10 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
include ApiHelpers
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin, name: 'root') }
- let(:namespace) { create(:namespace, name: 'gitlab-test' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'lorem-ipsum') }
+ let_it_be(:admin) { create(:admin, name: 'root') }
+ let_it_be(:namespace) { create(:namespace, name: 'gitlab-test' )}
+ let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'lorem-ipsum') }
+ let_it_be(:mr) { create(:merge_request, source_project: project) }
before(:all) do
clean_frontend_fixtures('api/merge_requests')
@@ -21,4 +22,16 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
expect(response).to be_successful
end
+
+ it 'api/merge_requests/versions.json' do
+ get api("/projects/#{project.id}/merge_requests/#{mr.iid}/versions", admin)
+
+ expect(response).to be_successful
+ end
+
+ it 'api/merge_requests/changes.json' do
+ get api("/projects/#{project.id}/merge_requests/#{mr.iid}/changes", admin)
+
+ expect(response).to be_successful
+ end
end
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index acce3891ada..418912638f9 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -22,7 +22,6 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
let(:merge_request) do
create(
:merge_request,
- :with_diffs,
source_project: project,
target_project: project,
description: description
diff --git a/spec/frontend/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
index 6e07ef679f5..5ad4176f7b8 100644
--- a/spec/frontend/fixtures/merge_requests_diffs.rb
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -8,7 +8,7 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
let(:user) { project.owner }
- let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
build(:text_diff_position, :added,
diff --git a/spec/frontend/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
index 4270e38afcb..b4b7f0e332f 100644
--- a/spec/frontend/fixtures/pipelines.rb
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -12,7 +12,7 @@ RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :co
let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) }
let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
- let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') }
+ let!(:pipeline_without_commit) { create(:ci_pipeline, status: :success, project: project, sha: '0000') }
render_views
diff --git a/spec/frontend/frequent_items/components/app_spec.js b/spec/frontend/frequent_items/components/app_spec.js
index b74e4ac45cf..80059c4c87f 100644
--- a/spec/frontend/frequent_items/components/app_spec.js
+++ b/spec/frontend/frequent_items/components/app_spec.js
@@ -1,15 +1,16 @@
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { useRealDate } from 'helpers/fake_date';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import appComponent from '~/frequent_items/components/app.vue';
-import eventHub from '~/frequent_items/event_hub';
import { FREQUENT_ITEMS, HOUR_IN_MS } from '~/frequent_items/constants';
+import eventHub from '~/frequent_items/event_hub';
+import { createStore } from '~/frequent_items/store';
import { getTopFrequentItems } from '~/frequent_items/utils';
+import axios from '~/lib/utils/axios_utils';
import { currentSession, mockFrequentProjects, mockSearchedProjects } from '../mock_data';
-import { createStore } from '~/frequent_items/store';
useLocalStorageSpy();
@@ -93,23 +94,27 @@ describe('Frequent Items App Component', () => {
expect(projects.length).toBe(1);
});
- it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
- let projects;
- const newTimestamp = Date.now() + HOUR_IN_MS + 1;
+ describe('with real date', () => {
+ useRealDate();
- vm.logItemAccess(session.storageKey, session.project);
- projects = JSON.parse(storage[session.storageKey]);
+ it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
+ let projects;
+ const newTimestamp = Date.now() + HOUR_IN_MS + 1;
- expect(projects[0].frequency).toBe(1);
+ vm.logItemAccess(session.storageKey, session.project);
+ projects = JSON.parse(storage[session.storageKey]);
- vm.logItemAccess(session.storageKey, {
- ...session.project,
- lastAccessedOn: newTimestamp,
- });
- projects = JSON.parse(storage[session.storageKey]);
+ expect(projects[0].frequency).toBe(1);
+
+ vm.logItemAccess(session.storageKey, {
+ ...session.project,
+ lastAccessedOn: newTimestamp,
+ });
+ projects = JSON.parse(storage[session.storageKey]);
- expect(projects[0].frequency).toBe(2);
- expect(projects[0].lastAccessedOn).not.toBe(session.project.lastAccessedOn);
+ expect(projects[0].frequency).toBe(2);
+ expect(projects[0].lastAccessedOn).not.toBe(session.project.lastAccessedOn);
+ });
});
it('should always update project metadata', () => {
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 19095c4474e..66fb346cb38 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { trimText } from 'helpers/text_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
import { createStore } from '~/frequent_items/store';
import { mockProject } from '../mock_data';
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 96f73ab1468..bd0711005b3 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
-import { createStore } from '~/frequent_items/store';
import frequentItemsListComponent from '~/frequent_items/components/frequent_items_list.vue';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
+import { createStore } from '~/frequent_items/store';
import { mockFrequentProjects } from '../mock_data';
describe('FrequentItemsListComponent', () => {
diff --git a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
index cdd8b127676..0280fdb0ca2 100644
--- a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
@@ -1,8 +1,8 @@
+import { GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue';
import { createStore } from '~/frequent_items/store';
-import eventHub from '~/frequent_items/event_hub';
describe('FrequentItemsSearchInputComponent', () => {
let wrapper;
@@ -16,6 +16,8 @@ describe('FrequentItemsSearchInputComponent', () => {
propsData: { namespace },
});
+ const findSearchBoxByType = () => wrapper.find(GlSearchBoxByType);
+
beforeEach(() => {
store = createStore({ dropdownType: 'project' });
jest.spyOn(store, 'dispatch').mockImplementation(() => {});
@@ -33,59 +35,13 @@ describe('FrequentItemsSearchInputComponent', () => {
vm.$destroy();
});
- describe('methods', () => {
- describe('setFocus', () => {
- it('should set focus to search input', () => {
- jest.spyOn(vm.$refs.search, 'focus').mockImplementation(() => {});
-
- vm.setFocus();
-
- expect(vm.$refs.search.focus).toHaveBeenCalled();
- });
- });
- });
-
- describe('mounted', () => {
- it('should listen `dropdownOpen` event', (done) => {
- jest.spyOn(eventHub, '$on').mockImplementation(() => {});
- const vmX = createComponent().vm;
-
- vmX.$nextTick(() => {
- expect(eventHub.$on).toHaveBeenCalledWith(
- `${vmX.namespace}-dropdownOpen`,
- expect.any(Function),
- );
- done();
- });
- });
- });
-
- describe('beforeDestroy', () => {
- it('should unbind event listeners on eventHub', (done) => {
- const vmX = createComponent().vm;
- jest.spyOn(eventHub, '$off').mockImplementation(() => {});
-
- vmX.$mount();
- vmX.$destroy();
-
- vmX.$nextTick(() => {
- expect(eventHub.$off).toHaveBeenCalledWith(
- `${vmX.namespace}-dropdownOpen`,
- expect.any(Function),
- );
- done();
- });
- });
- });
-
describe('template', () => {
it('should render component element', () => {
expect(wrapper.classes()).toContain('search-input-container');
- expect(wrapper.find('input.form-control').exists()).toBe(true);
- expect(wrapper.find('.search-icon').exists()).toBe(true);
- expect(wrapper.find('input.form-control').attributes('placeholder')).toBe(
- 'Search your projects',
- );
+ expect(findSearchBoxByType().exists()).toBe(true);
+ expect(findSearchBoxByType().attributes()).toMatchObject({
+ placeholder: 'Search your projects',
+ });
});
});
@@ -96,9 +52,7 @@ describe('FrequentItemsSearchInputComponent', () => {
const value = 'my project';
- const input = wrapper.find('input');
- input.setValue(value);
- input.trigger('input');
+ findSearchBoxByType().vm.$emit('input', value);
await wrapper.vm.$nextTick();
diff --git a/spec/frontend/frequent_items/store/actions_spec.js b/spec/frontend/frequent_items/store/actions_spec.js
index 351fde25f49..dacfc7ce707 100644
--- a/spec/frontend/frequent_items/store/actions_spec.js
+++ b/spec/frontend/frequent_items/store/actions_spec.js
@@ -1,10 +1,10 @@
-import testAction from 'helpers/vuex_action_helper';
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import AccessorUtilities from '~/lib/utils/accessor';
+import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/frequent_items/store/actions';
import * as types from '~/frequent_items/store/mutation_types';
import state from '~/frequent_items/store/state';
+import AccessorUtilities from '~/lib/utils/accessor';
+import axios from '~/lib/utils/axios_utils';
import {
mockNamespace,
mockStorageKey,
diff --git a/spec/frontend/frequent_items/store/getters_spec.js b/spec/frontend/frequent_items/store/getters_spec.js
index 1cd12eb6832..97732cd95fc 100644
--- a/spec/frontend/frequent_items/store/getters_spec.js
+++ b/spec/frontend/frequent_items/store/getters_spec.js
@@ -1,5 +1,5 @@
-import state from '~/frequent_items/store/state';
import * as getters from '~/frequent_items/store/getters';
+import state from '~/frequent_items/store/state';
describe('Frequent Items Dropdown Store Getters', () => {
let mockedState;
diff --git a/spec/frontend/frequent_items/store/mutations_spec.js b/spec/frontend/frequent_items/store/mutations_spec.js
index d36964b2600..e593c9fae58 100644
--- a/spec/frontend/frequent_items/store/mutations_spec.js
+++ b/spec/frontend/frequent_items/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/frequent_items/store/state';
-import mutations from '~/frequent_items/store/mutations';
import * as types from '~/frequent_items/store/mutation_types';
+import mutations from '~/frequent_items/store/mutations';
+import state from '~/frequent_items/store/state';
import {
mockNamespace,
mockStorageKey,
diff --git a/spec/frontend/frequent_items/utils_spec.js b/spec/frontend/frequent_items/utils_spec.js
index 181dd9268dc..a7ab18b0d10 100644
--- a/spec/frontend/frequent_items/utils_spec.js
+++ b/spec/frontend/frequent_items/utils_spec.js
@@ -1,11 +1,11 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { HOUR_IN_MS, FREQUENT_ITEMS } from '~/frequent_items/constants';
import {
isMobile,
getTopFrequentItems,
updateExistingFrequentItem,
sanitizeItem,
} from '~/frequent_items/utils';
-import { HOUR_IN_MS, FREQUENT_ITEMS } from '~/frequent_items/constants';
import { mockProject, unsortedFrequentItems, sortedFrequentItems } from './mock_data';
describe('Frequent Items utils spec', () => {
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index c2ff66f6afc..08368e1f2ca 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -1,16 +1,12 @@
/* eslint no-param-reassign: "off" */
-
+import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
-import { emojiFixtureMap, initEmojiMock, describeEmojiFields } from 'helpers/emoji';
-import '~/lib/utils/jquery_at_who';
import GfmAutoComplete, { membersBeforeSave } from 'ee_else_ce/gfm_auto_complete';
-
-import { TEST_HOST } from 'helpers/test_constants';
+import { initEmojiMock } from 'helpers/emoji';
+import '~/lib/utils/jquery_at_who';
import { getJSONFixture } from 'helpers/fixtures';
-
+import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
-
-import MockAdapter from 'axios-mock-adapter';
import AjaxCache from '~/lib/utils/ajax_cache';
import axios from '~/lib/utils/axios_utils';
@@ -493,7 +489,7 @@ describe('GfmAutoComplete', () => {
username: 'my-group',
avatarTag: '<div class="avatar rect-avatar center avatar-inline s26">M</div>',
title: 'My Group (2)',
- search: 'my-group My Group',
+ search: 'MyGroup my-group',
icon: '',
},
]);
@@ -506,7 +502,7 @@ describe('GfmAutoComplete', () => {
avatarTag:
'<img src="./group.jpg" alt="my-group" class="avatar rect-avatar avatar-inline center s26"/>',
title: 'My Group (2)',
- search: 'my-group My Group',
+ search: 'MyGroup my-group',
icon: '',
},
]);
@@ -519,7 +515,7 @@ describe('GfmAutoComplete', () => {
avatarTag:
'<img src="./group.jpg" alt="my-group" class="avatar rect-avatar avatar-inline center s26"/>',
title: 'My Group',
- search: 'my-group My Group',
+ search: 'MyGroup my-group',
icon:
'<svg class="s16 vertical-align-middle gl-ml-2"><use xlink:href="undefined#notifications-off" /></svg>',
},
@@ -537,7 +533,7 @@ describe('GfmAutoComplete', () => {
avatarTag:
'<img src="./users.jpg" alt="my-user" class="avatar avatar-inline center s26"/>',
title: 'My User',
- search: 'my-user My User',
+ search: 'MyUser my-user',
icon: '',
},
]);
@@ -717,16 +713,20 @@ describe('GfmAutoComplete', () => {
});
describe('emoji', () => {
- const { atom, heart, star } = emojiFixtureMap;
- const assertInserted = ({ input, subject, emoji }) =>
- expect(subject).toBe(`:${emoji?.name || input}:`);
- const assertTemplated = ({ input, subject, emoji, field }) =>
- expect(subject.replace(/\s+/g, ' ')).toBe(
- `<li>${field || input} <gl-emoji data-name="${emoji?.name || input}"></gl-emoji> </li>`,
- );
-
let mock;
+ const mockItem = {
+ 'atwho-at': ':',
+ emoji: {
+ c: 'symbols',
+ d: 'negative squared ab',
+ e: '🆎',
+ name: 'ab',
+ u: '6.0',
+ },
+ fieldValue: 'ab',
+ };
+
beforeEach(async () => {
mock = await initEmojiMock();
@@ -738,90 +738,22 @@ describe('GfmAutoComplete', () => {
mock.restore();
});
- describe.each`
- name | inputFormat | assert
- ${'insertTemplateFunction'} | ${(name) => ({ name })} | ${assertInserted}
- ${'templateFunction'} | ${(name) => name} | ${assertTemplated}
- `('Emoji.$name', ({ name, inputFormat, assert }) => {
- const execute = (accessor, input, emoji) =>
- assert({
- input,
- emoji,
- field: accessor && accessor(emoji),
- subject: GfmAutoComplete.Emoji[name](inputFormat(input)),
- });
-
- describeEmojiFields('for $field', ({ accessor }) => {
- it('should work with lowercase', () => {
- execute(accessor, accessor(atom), atom);
- });
-
- it('should work with uppercase', () => {
- execute(accessor, accessor(atom).toUpperCase(), atom);
- });
-
- it('should work with partial value', () => {
- execute(accessor, accessor(atom).slice(1), atom);
- });
- });
-
- it('should work with unicode value', () => {
- execute(null, atom.moji, atom);
- });
+ describe('Emoji.templateFunction', () => {
+ it('should return a correct template', () => {
+ const actual = GfmAutoComplete.Emoji.templateFunction(mockItem);
+ const glEmojiTag = `<gl-emoji data-name="${mockItem.emoji.name}"></gl-emoji>`;
+ const expected = `<li>${mockItem.fieldValue} ${glEmojiTag}</li>`;
- it('should pass through unknown value', () => {
- execute(null, 'foo bar baz');
+ expect(actual).toBe(expected);
});
});
- const expectEmojiOrder = (first, second) => {
- const keys = Object.keys(emojiFixtureMap);
- const firstIndex = keys.indexOf(first);
- const secondIndex = keys.indexOf(second);
- expect(firstIndex).toBeGreaterThanOrEqual(0);
- expect(secondIndex).toBeGreaterThanOrEqual(0);
- expect(firstIndex).toBeLessThan(secondIndex);
- };
-
describe('Emoji.insertTemplateFunction', () => {
- it('should map ":heart" to :heart: [regression]', () => {
- // the bug mapped heart to black_heart because the latter sorted first
- expectEmojiOrder('black_heart', 'heart');
-
- const item = GfmAutoComplete.Emoji.insertTemplateFunction({ name: 'heart' });
- expect(item).toEqual(`:${heart.name}:`);
- });
-
- it('should map ":star" to :star: [regression]', () => {
- // the bug mapped star to custard because the latter sorted first
- expectEmojiOrder('custard', 'star');
-
- const item = GfmAutoComplete.Emoji.insertTemplateFunction({ name: 'star' });
- expect(item).toEqual(`:${star.name}:`);
- });
- });
-
- describe('Emoji.templateFunction', () => {
- it('should map ":heart" to ❤ [regression]', () => {
- // the bug mapped heart to black_heart because the latter sorted first
- expectEmojiOrder('black_heart', 'heart');
-
- const item = GfmAutoComplete.Emoji.templateFunction('heart')
- .replace(/(<gl-emoji)\s+(data-name)/, '$1 $2')
- .replace(/>\s+|\s+</g, (s) => s.trim());
- expect(item).toEqual(
- `<li>${heart.name}<gl-emoji data-name="${heart.name}"></gl-emoji></li>`,
- );
- });
-
- it('should map ":star" to ⭐ [regression]', () => {
- // the bug mapped star to custard because the latter sorted first
- expectEmojiOrder('custard', 'star');
+ it('should return a correct template', () => {
+ const actual = GfmAutoComplete.Emoji.insertTemplateFunction(mockItem);
+ const expected = `:${mockItem.emoji.name}:`;
- const item = GfmAutoComplete.Emoji.templateFunction('star')
- .replace(/(<gl-emoji)\s+(data-name)/, '$1 $2')
- .replace(/>\s+|\s+</g, (s) => s.trim());
- expect(item).toEqual(`<li>${star.name}<gl-emoji data-name="${star.name}"></gl-emoji></li>`);
+ expect(actual).toBe(expected);
});
});
});
diff --git a/spec/frontend/gl_form_spec.js b/spec/frontend/gl_form_spec.js
index d9a01f7bcc1..07487fbb60e 100644
--- a/spec/frontend/gl_form_spec.js
+++ b/spec/frontend/gl_form_spec.js
@@ -1,5 +1,5 @@
-import $ from 'jquery';
import autosize from 'autosize';
+import $ from 'jquery';
import GLForm from '~/gl_form';
import '~/lib/utils/text_utility';
import '~/lib/utils/common_utils';
diff --git a/spec/frontend/gpg_badges_spec.js b/spec/frontend/gpg_badges_spec.js
index 7c1f83e577c..cd2cc88fa5a 100644
--- a/spec/frontend/gpg_badges_spec.js
+++ b/spec/frontend/gpg_badges_spec.js
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants';
-import axios from '~/lib/utils/axios_utils';
import GpgBadges from '~/gpg_badges';
+import axios from '~/lib/utils/axios_utils';
describe('GpgBadges', () => {
let mock;
diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
index e880f585daa..0fc4343ec3c 100644
--- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
+++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
@@ -8,13 +8,13 @@ exports[`grafana integration component default state to match the default snapsh
<div
class="settings-header"
>
- <h3
- class="js-section-header h4"
+ <h4
+ class="js-section-header"
>
Grafana authentication
- </h3>
+ </h4>
<gl-button-stub
buttontextclasses=""
diff --git a/spec/frontend/grafana_integration/components/grafana_integration_spec.js b/spec/frontend/grafana_integration/components/grafana_integration_spec.js
index df88a336c09..ad1260d8030 100644
--- a/spec/frontend/grafana_integration/components/grafana_integration_spec.js
+++ b/spec/frontend/grafana_integration/components/grafana_integration_spec.js
@@ -1,11 +1,11 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
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';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash');
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 4ec739122c8..78950a8fe20 100644
--- a/spec/frontend/group_settings/components/shared_runners_form_spec.js
+++ b/spec/frontend/group_settings/components/shared_runners_form_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MockAxiosAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import SharedRunnersForm from '~/group_settings/components/shared_runners_form.vue';
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index 9244e4f331e..e559c9519f2 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -1,16 +1,16 @@
import '~/flash';
-import Vue from 'vue';
-import AxiosMockAdapter from 'axios-mock-adapter';
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import appComponent from '~/groups/components/app.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
import eventHub from '~/groups/event_hub';
-import GroupsStore from '~/groups/store/groups_store';
import GroupsService from '~/groups/service/groups_service';
+import GroupsStore from '~/groups/store/groups_store';
+import axios from '~/lib/utils/axios_utils';
import * as urlUtilities from '~/lib/utils/url_utility';
import {
@@ -60,8 +60,8 @@ describe('AppComponent', () => {
beforeEach(() => {
mock = new AxiosMockAdapter(axios);
mock.onGet('/dashboard/groups.json').reply(200, mockGroups);
- Vue.component('group-folder', groupFolderComponent);
- Vue.component('group-item', groupItemComponent);
+ Vue.component('GroupFolder', groupFolderComponent);
+ Vue.component('GroupItem', groupItemComponent);
createShallowComponent();
getGroupsSpy = jest.spyOn(vm.service, 'getGroups');
diff --git a/spec/frontend/groups/components/group_folder_spec.js b/spec/frontend/groups/components/group_folder_spec.js
index a40fa9bece8..1d8e10479b6 100644
--- a/spec/frontend/groups/components/group_folder_spec.js
+++ b/spec/frontend/groups/components/group_folder_spec.js
@@ -19,7 +19,7 @@ describe('GroupFolderComponent', () => {
let vm;
beforeEach(() => {
- Vue.component('group-item', groupItemComponent);
+ Vue.component('GroupItem', groupItemComponent);
vm = createComponent();
vm.$mount();
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index d70ea709dee..4fcc9bafa46 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
-import groupItemComponent from '~/groups/components/group_item.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
-import { getGroupItemMicrodata } from '~/groups/store/utils';
+import groupItemComponent from '~/groups/components/group_item.vue';
import eventHub from '~/groups/event_hub';
+import { getGroupItemMicrodata } from '~/groups/store/utils';
import * as urlUtilities from '~/lib/utils/url_utility';
import { mockParentGroupItem, mockChildren } from '../mock_data';
@@ -20,7 +20,7 @@ describe('GroupItemComponent', () => {
let vm;
beforeEach(() => {
- Vue.component('group-folder', groupFolderComponent);
+ Vue.component('GroupFolder', groupFolderComponent);
vm = createComponent();
diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js
index 6205400eb03..dc1a10639fc 100644
--- a/spec/frontend/groups/components/groups_spec.js
+++ b/spec/frontend/groups/components/groups_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
-import groupsComponent from '~/groups/components/groups.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
+import groupsComponent from '~/groups/components/groups.vue';
import eventHub from '~/groups/event_hub';
import { mockGroups, mockPageInfo } from '../mock_data';
@@ -22,8 +22,8 @@ describe('GroupsComponent', () => {
let vm;
beforeEach(() => {
- Vue.component('group-folder', groupFolderComponent);
- Vue.component('group-item', groupItemComponent);
+ Vue.component('GroupFolder', groupFolderComponent);
+ Vue.component('GroupItem', groupItemComponent);
vm = createComponent();
diff --git a/spec/frontend/groups/components/invite_members_banner_spec.js b/spec/frontend/groups/components/invite_members_banner_spec.js
index 4e69f3cd433..9a2068a27a1 100644
--- a/spec/frontend/groups/components/invite_members_banner_spec.js
+++ b/spec/frontend/groups/components/invite_members_banner_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlBanner, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { setCookie, parseBoolean } from '~/lib/utils/common_utils';
import InviteMembersBanner from '~/groups/components/invite_members_banner.vue';
+import { setCookie, parseBoolean } from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/common_utils');
diff --git a/spec/frontend/groups/components/item_actions_spec.js b/spec/frontend/groups/components/item_actions_spec.js
index 9adbc9abe13..ffbdf9b1aa6 100644
--- a/spec/frontend/groups/components/item_actions_spec.js
+++ b/spec/frontend/groups/components/item_actions_spec.js
@@ -66,6 +66,22 @@ describe('ItemActions', () => {
});
});
+ it('emits `showLeaveGroupModal` event with the correct prefix if `action` prop is passed', () => {
+ const group = {
+ ...mockParentGroupItem,
+ canEdit: true,
+ canLeave: true,
+ };
+ createComponent({
+ group,
+ action: 'test',
+ });
+ jest.spyOn(eventHub, '$emit');
+ findLeaveGroupBtn().vm.$emit('click', { stopPropagation: () => {} });
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('testshowLeaveGroupModal', group, parentGroup);
+ });
+
it('does not render leave button if group can not be left', () => {
createComponent({
group: {
diff --git a/spec/frontend/groups/components/item_caret_spec.js b/spec/frontend/groups/components/item_caret_spec.js
index b2915607a06..cbe1f21d6e2 100644
--- a/spec/frontend/groups/components/item_caret_spec.js
+++ b/spec/frontend/groups/components/item_caret_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ItemCaret from '~/groups/components/item_caret.vue';
describe('ItemCaret', () => {
diff --git a/spec/frontend/groups/components/item_stats_value_spec.js b/spec/frontend/groups/components/item_stats_value_spec.js
index bca233883af..98186120a81 100644
--- a/spec/frontend/groups/components/item_stats_value_spec.js
+++ b/spec/frontend/groups/components/item_stats_value_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ItemStatsValue from '~/groups/components/item_stats_value.vue';
describe('ItemStatsValue', () => {
diff --git a/spec/frontend/groups/components/item_type_icon_spec.js b/spec/frontend/groups/components/item_type_icon_spec.js
index 5e7056be218..9310943841e 100644
--- a/spec/frontend/groups/components/item_type_icon_spec.js
+++ b/spec/frontend/groups/components/item_type_icon_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ItemTypeIcon from '~/groups/components/item_type_icon.vue';
import { ITEM_TYPE } from '../mock_data';
diff --git a/spec/frontend/groups/members/mock_data.js b/spec/frontend/groups/members/mock_data.js
deleted file mode 100644
index b84c9c6d446..00000000000
--- a/spec/frontend/groups/members/mock_data.js
+++ /dev/null
@@ -1,33 +0,0 @@
-export const membersJsonString =
- '[{"requested_at":null,"can_update":true,"can_remove":true,"can_override":false,"access_level":{"integer_value":50,"string_value":"Owner"},"source":{"id":323,"name":"My group / my subgroup","web_url":"http://127.0.0.1:3000/groups/my-group/my-subgroup"},"user":{"id":1,"name":"Administrator","username":"root","web_url":"http://127.0.0.1:3000/root","avatar_url":"https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80\u0026d=identicon","blocked":false,"two_factor_enabled":false},"id":524,"created_at":"2020-08-21T21:33:27.631Z","expires_at":null,"using_license":false,"group_sso":false,"group_managed_account":false}]';
-
-export const membersParsed = [
- {
- requestedAt: null,
- canUpdate: true,
- canRemove: true,
- canOverride: false,
- accessLevel: { integerValue: 50, stringValue: 'Owner' },
- source: {
- id: 323,
- name: 'My group / my subgroup',
- webUrl: 'http://127.0.0.1:3000/groups/my-group/my-subgroup',
- },
- user: {
- id: 1,
- name: 'Administrator',
- username: 'root',
- webUrl: 'http://127.0.0.1:3000/root',
- avatarUrl:
- 'https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80&d=identicon',
- blocked: false,
- twoFactorEnabled: false,
- },
- id: 524,
- createdAt: '2020-08-21T21:33:27.631Z',
- expiresAt: null,
- usingLicense: false,
- groupSso: false,
- groupManagedAccount: false,
- },
-];
diff --git a/spec/frontend/groups/members/utils_spec.js b/spec/frontend/groups/members/utils_spec.js
index 68945174e9d..0912e66e3e8 100644
--- a/spec/frontend/groups/members/utils_spec.js
+++ b/spec/frontend/groups/members/utils_spec.js
@@ -1,53 +1,14 @@
-import { membersJsonString, membersParsed } from './mock_data';
-import {
- parseDataAttributes,
- memberRequestFormatter,
- groupLinkRequestFormatter,
-} from '~/groups/members/utils';
+import { groupMemberRequestFormatter } from '~/groups/members/utils';
describe('group member utils', () => {
- describe('parseDataAttributes', () => {
- let el;
-
- beforeEach(() => {
- el = document.createElement('div');
- el.setAttribute('data-members', membersJsonString);
- el.setAttribute('data-group-id', '234');
- el.setAttribute('data-can-manage-members', 'true');
- });
-
- afterEach(() => {
- el = null;
- });
-
- it('correctly parses the data attributes', () => {
- expect(parseDataAttributes(el)).toEqual({
- members: membersParsed,
- sourceId: 234,
- canManageMembers: true,
- });
- });
- });
-
- describe('memberRequestFormatter', () => {
+ describe('groupMemberRequestFormatter', () => {
it('returns expected format', () => {
expect(
- memberRequestFormatter({
+ groupMemberRequestFormatter({
accessLevel: 50,
expires_at: '2020-10-16',
}),
).toEqual({ group_member: { access_level: 50, expires_at: '2020-10-16' } });
});
});
-
- describe('groupLinkRequestFormatter', () => {
- it('returns expected format', () => {
- expect(
- groupLinkRequestFormatter({
- accessLevel: 50,
- expires_at: '2020-10-16',
- }),
- ).toEqual({ group_link: { group_access: 50, expires_at: '2020-10-16' } });
- });
- });
});
diff --git a/spec/frontend/groups/service/groups_service_spec.js b/spec/frontend/groups/service/groups_service_spec.js
index 38a565eba01..e037a6df1e2 100644
--- a/spec/frontend/groups/service/groups_service_spec.js
+++ b/spec/frontend/groups/service/groups_service_spec.js
@@ -1,6 +1,6 @@
+import GroupsService from '~/groups/service/groups_service';
import axios from '~/lib/utils/axios_utils';
-import GroupsService from '~/groups/service/groups_service';
import { mockEndpoint, mockParentGroupItem } from '../mock_data';
describe('GroupsService', () => {
diff --git a/spec/frontend/ide/commit_icon_spec.js b/spec/frontend/ide/commit_icon_spec.js
index 0dfcae00298..3acdfec5393 100644
--- a/spec/frontend/ide/commit_icon_spec.js
+++ b/spec/frontend/ide/commit_icon_spec.js
@@ -1,6 +1,6 @@
+import getCommitIconMap from '~/ide/commit_icon';
import { commitItemIconMap } from '~/ide/constants';
import { decorateData } from '~/ide/stores/utils';
-import getCommitIconMap from '~/ide/commit_icon';
const createFile = (name = 'name', id = name, type = '', parent = null) =>
decorateData({
diff --git a/spec/frontend/ide/components/activity_bar_spec.js b/spec/frontend/ide/components/activity_bar_spec.js
index 1a4b6ca0b71..657817eb3d8 100644
--- a/spec/frontend/ide/components/activity_bar_spec.js
+++ b/spec/frontend/ide/components/activity_bar_spec.js
@@ -1,14 +1,16 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
-import { leftSidebarViews } from '~/ide/constants';
import ActivityBar from '~/ide/components/activity_bar.vue';
+import { leftSidebarViews } from '~/ide/constants';
+import { createStore } from '~/ide/stores';
describe('IDE activity bar', () => {
const Component = Vue.extend(ActivityBar);
let vm;
let store;
+ const findChangesBadge = () => vm.$el.querySelector('.badge');
+
beforeEach(() => {
store = createStore();
@@ -69,4 +71,19 @@ describe('IDE activity bar', () => {
});
});
});
+
+ describe('changes badge', () => {
+ it('is rendered when files are staged', () => {
+ store.state.stagedFiles = [{ path: '/path/to/file' }];
+ vm.$mount();
+
+ expect(findChangesBadge()).toBeTruthy();
+ expect(findChangesBadge().textContent.trim()).toBe('1');
+ });
+
+ it('is not rendered when no changes are present', () => {
+ vm.$mount();
+ expect(findChangesBadge()).toBeFalsy();
+ });
+ });
});
diff --git a/spec/frontend/ide/components/branches/item_spec.js b/spec/frontend/ide/components/branches/item_spec.js
index f1aa9187a8d..f90c298c401 100644
--- a/spec/frontend/ide/components/branches/item_spec.js
+++ b/spec/frontend/ide/components/branches/item_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
-import { createStore } from '~/ide/stores';
-import { createRouter } from '~/ide/ide_router';
+import { shallowMount } from '@vue/test-utils';
import Item from '~/ide/components/branches/item.vue';
+import { createRouter } from '~/ide/ide_router';
+import { createStore } from '~/ide/stores';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import { projectData } from '../../mock_data';
diff --git a/spec/frontend/ide/components/branches/search_list_spec.js b/spec/frontend/ide/components/branches/search_list_spec.js
index 85776f8cc0e..0efa7af2c6c 100644
--- a/spec/frontend/ide/components/branches/search_list_spec.js
+++ b/spec/frontend/ide/components/branches/search_list_spec.js
@@ -1,9 +1,9 @@
+import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
-import { __ } from '~/locale';
-import List from '~/ide/components/branches/search_list.vue';
import Item from '~/ide/components/branches/item.vue';
+import List from '~/ide/components/branches/search_list.vue';
+import { __ } from '~/locale';
import { branches } from '../../mock_data';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
index 91751bd34ea..c4dccf26af3 100644
--- a/spec/frontend/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
@@ -1,9 +1,12 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { projectData, branches } from 'jest/ide/mock_data';
-import { createStore } from '~/ide/stores';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
-import consts from '~/ide/stores/modules/commit/constants';
+import { createStore } from '~/ide/stores';
+import {
+ COMMIT_TO_NEW_BRANCH,
+ COMMIT_TO_CURRENT_BRANCH,
+} from '~/ide/stores/modules/commit/constants';
const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction';
@@ -126,16 +129,16 @@ describe('IDE commit sidebar actions', () => {
it.each`
input | expectedOption
- ${{ currentBranchId: BRANCH_DEFAULT }} | ${consts.COMMIT_TO_NEW_BRANCH}
- ${{ currentBranchId: BRANCH_DEFAULT, emptyRepo: true }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
- ${{ currentBranchId: BRANCH_PROTECTED, hasMR: true }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
- ${{ currentBranchId: BRANCH_PROTECTED, hasMR: false }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
- ${{ currentBranchId: BRANCH_PROTECTED_NO_ACCESS, hasMR: true }} | ${consts.COMMIT_TO_NEW_BRANCH}
- ${{ currentBranchId: BRANCH_PROTECTED_NO_ACCESS, hasMR: false }} | ${consts.COMMIT_TO_NEW_BRANCH}
- ${{ currentBranchId: BRANCH_REGULAR, hasMR: true }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
- ${{ currentBranchId: BRANCH_REGULAR, hasMR: false }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
- ${{ currentBranchId: BRANCH_REGULAR_NO_ACCESS, hasMR: true }} | ${consts.COMMIT_TO_NEW_BRANCH}
- ${{ currentBranchId: BRANCH_REGULAR_NO_ACCESS, hasMR: false }} | ${consts.COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_DEFAULT }} | ${COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_DEFAULT, emptyRepo: true }} | ${COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED, hasMR: true }} | ${COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED, hasMR: false }} | ${COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED_NO_ACCESS, hasMR: true }} | ${COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED_NO_ACCESS, hasMR: false }} | ${COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR, hasMR: true }} | ${COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR, hasMR: false }} | ${COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR_NO_ACCESS, hasMR: true }} | ${COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR_NO_ACCESS, hasMR: false }} | ${COMMIT_TO_NEW_BRANCH}
`(
'with $input, it dispatches update commit action with $expectedOption',
({ input, expectedOption }) => {
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 ff780939026..50635ffe894 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,7 @@
-import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
-import { createStore } from '~/ide/stores';
+import Vuex from 'vuex';
import EditorHeader from '~/ide/components/commit_sidebar/editor_header.vue';
+import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
const localVue = createLocalVue();
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 c1dab4a04b6..4f81c0aa5d3 100644
--- a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import emptyState from '~/ide/components/commit_sidebar/empty_state.vue';
+import { createStore } from '~/ide/stores';
describe('IDE commit panel empty state', () => {
let vm;
diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js
index abd7e3bb8fc..2b567816ce8 100644
--- a/spec/frontend/ide/components/commit_sidebar/form_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js
@@ -1,11 +1,12 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
-import { getByText } from '@testing-library/dom';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { projectData } from 'jest/ide/mock_data';
+import { stubComponent } from 'helpers/stub_component';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import waitForPromises from 'helpers/wait_for_promises';
-import { createStore } from '~/ide/stores';
-import consts from '~/ide/stores/modules/commit/constants';
+import { projectData } from 'jest/ide/mock_data';
import CommitForm from '~/ide/components/commit_sidebar/form.vue';
+import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue';
import { leftSidebarViews } from '~/ide/constants';
import {
createCodeownersCommitError,
@@ -13,258 +14,287 @@ import {
createBranchChangedCommitError,
branchAlreadyExistsCommitError,
} from '~/ide/lib/errors';
+import { createStore } from '~/ide/stores';
+import { COMMIT_TO_NEW_BRANCH } from '~/ide/stores/modules/commit/constants';
describe('IDE commit form', () => {
- const Component = Vue.extend(CommitForm);
- let vm;
+ let wrapper;
let store;
- const beginCommitButton = () => vm.$el.querySelector('[data-testid="begin-commit-button"]');
+ const createComponent = () => {
+ wrapper = shallowMount(CommitForm, {
+ store,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ stubs: {
+ GlModal: stubComponent(GlModal),
+ },
+ });
+ };
+
+ const setLastCommitMessage = (msg) => {
+ store.state.lastCommitMsg = msg;
+ };
+ const goToCommitView = () => {
+ store.state.currentActivityView = leftSidebarViews.commit.name;
+ };
+ const goToEditView = () => {
+ store.state.currentActivityView = leftSidebarViews.edit.name;
+ };
+ const findBeginCommitButton = () => wrapper.find('[data-testid="begin-commit-button"]');
+ const findBeginCommitButtonTooltip = () =>
+ wrapper.find('[data-testid="begin-commit-button-tooltip"]');
+ const findBeginCommitButtonData = () => ({
+ disabled: findBeginCommitButton().props('disabled'),
+ tooltip: getBinding(findBeginCommitButtonTooltip().element, 'gl-tooltip').value.title,
+ });
+ const findCommitButton = () => wrapper.find('[data-testid="commit-button"]');
+ const findCommitButtonTooltip = () => wrapper.find('[data-testid="commit-button-tooltip"]');
+ const findCommitButtonData = () => ({
+ disabled: findCommitButton().props('disabled'),
+ tooltip: getBinding(findCommitButtonTooltip().element, 'gl-tooltip').value.title,
+ });
+ const clickCommitButton = () => findCommitButton().vm.$emit('click');
+ const findForm = () => wrapper.find('form');
+ const submitForm = () => findForm().trigger('submit');
+ const findCommitMessageInput = () => wrapper.find(CommitMessageField);
+ const setCommitMessageInput = (val) => findCommitMessageInput().vm.$emit('input', val);
+ const findDiscardDraftButton = () => wrapper.find('[data-testid="discard-draft"]');
beforeEach(() => {
store = createStore();
- store.state.changedFiles.push('test');
+ store.state.stagedFiles.push('test');
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
- Vue.set(store.state.projects, 'abcproject', { ...projectData });
-
- vm = createComponentWithStore(Component, store).$mount();
+ Vue.set(store.state.projects, 'abcproject', {
+ ...projectData,
+ userPermissions: { pushCode: true },
+ });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
- it('enables begin commit button when there are changes', () => {
- expect(beginCommitButton()).not.toHaveAttr('disabled');
- });
+ // Notes:
+ // - When there are no changes, there is no commit button so there's nothing to test :)
+ describe.each`
+ desc | stagedFiles | userPermissions | viewFn | buttonFn | disabled | tooltip
+ ${'when there are no changes'} | ${[]} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${''}
+ ${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${false} | ${''}
+ ${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToCommitView} | ${findCommitButtonData} | ${false} | ${''}
+ ${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${CommitForm.MSG_CANNOT_PUSH_CODE}
+ ${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToCommitView} | ${findCommitButtonData} | ${true} | ${CommitForm.MSG_CANNOT_PUSH_CODE}
+ `('$desc', ({ stagedFiles, userPermissions, viewFn, buttonFn, disabled, tooltip }) => {
+ beforeEach(async () => {
+ store.state.stagedFiles = stagedFiles;
+ store.state.projects.abcproject.userPermissions = userPermissions;
+
+ createComponent();
+ });
- it('disables begin commit button when there are no changes', async () => {
- store.state.changedFiles = [];
- await vm.$nextTick();
+ it(`at view=${viewFn.name}, ${buttonFn.name} has disabled=${disabled} tooltip=${tooltip}`, async () => {
+ viewFn();
- expect(beginCommitButton()).toHaveAttr('disabled');
+ await wrapper.vm.$nextTick();
+
+ expect(buttonFn()).toEqual({
+ disabled,
+ tooltip,
+ });
+ });
});
- describe('compact', () => {
- beforeEach(() => {
- vm.isCompact = true;
+ describe('on edit tab', () => {
+ beforeEach(async () => {
+ // Test that we react to switching to compact view.
+ goToCommitView();
+
+ createComponent();
- return vm.$nextTick();
+ goToEditView();
+
+ await wrapper.vm.$nextTick();
});
it('renders commit button in compact mode', () => {
- expect(beginCommitButton()).not.toBeNull();
- expect(beginCommitButton().textContent).toContain('Commit');
+ expect(findBeginCommitButton().exists()).toBe(true);
+ expect(findBeginCommitButton().text()).toBe('Commit…');
});
it('does not render form', () => {
- expect(vm.$el.querySelector('form')).toBeNull();
+ expect(findForm().exists()).toBe(false);
});
it('renders overview text', () => {
- vm.$store.state.stagedFiles.push('test');
-
- return vm.$nextTick(() => {
- expect(vm.$el.querySelector('p').textContent).toContain('1 changed file');
- });
+ expect(wrapper.find('p').text()).toBe('1 changed file');
});
- it('shows form when clicking commit button', () => {
- beginCommitButton().click();
-
- return vm.$nextTick(() => {
- expect(vm.$el.querySelector('form')).not.toBeNull();
- });
- });
+ it('when begin commit button is clicked, shows form', async () => {
+ findBeginCommitButton().vm.$emit('click');
- it('toggles activity bar view when clicking commit button', () => {
- beginCommitButton().click();
+ await wrapper.vm.$nextTick();
- return vm.$nextTick(() => {
- expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name);
- });
+ expect(findForm().exists()).toBe(true);
});
- it('collapses if lastCommitMsg is set to empty and current view is not commit view', async () => {
- store.state.lastCommitMsg = 'abc';
- store.state.currentActivityView = leftSidebarViews.edit.name;
- await vm.$nextTick();
-
- // if commit message is set, form is uncollapsed
- expect(vm.isCompact).toBe(false);
+ it('when begin commit button is clicked, sets activity view', async () => {
+ findBeginCommitButton().vm.$emit('click');
- store.state.lastCommitMsg = '';
- await vm.$nextTick();
+ await wrapper.vm.$nextTick();
- // collapsed when set to empty
- expect(vm.isCompact).toBe(true);
+ expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name);
});
- it('collapses if in commit view but there are no changes and vice versa', async () => {
- store.state.currentActivityView = leftSidebarViews.commit.name;
- await vm.$nextTick();
+ it('collapses if lastCommitMsg is set to empty and current view is not commit view', async () => {
+ // Test that it expands when lastCommitMsg is set
+ setLastCommitMessage('test');
+ goToEditView();
- // expanded by default if there are changes
- expect(vm.isCompact).toBe(false);
+ await wrapper.vm.$nextTick();
- store.state.changedFiles = [];
- await vm.$nextTick();
+ expect(findForm().exists()).toBe(true);
- expect(vm.isCompact).toBe(true);
+ // Now test that it collapses when lastCommitMsg is cleared
+ setLastCommitMessage('');
- store.state.changedFiles.push('test');
- await vm.$nextTick();
+ await wrapper.vm.$nextTick();
- // uncollapsed once again
- expect(vm.isCompact).toBe(false);
+ expect(findForm().exists()).toBe(false);
});
+ });
- it('collapses if switched from commit view to edit view and vice versa', async () => {
- store.state.currentActivityView = leftSidebarViews.edit.name;
- await vm.$nextTick();
-
- expect(vm.isCompact).toBe(true);
+ describe('on commit tab when window height is less than MAX_WINDOW_HEIGHT', () => {
+ let oldHeight;
- store.state.currentActivityView = leftSidebarViews.commit.name;
- await vm.$nextTick();
+ beforeEach(async () => {
+ oldHeight = window.innerHeight;
+ window.innerHeight = 700;
- expect(vm.isCompact).toBe(false);
+ createComponent();
- store.state.currentActivityView = leftSidebarViews.edit.name;
- await vm.$nextTick();
+ goToCommitView();
- expect(vm.isCompact).toBe(true);
+ await wrapper.vm.$nextTick();
});
- describe('when window height is less than MAX_WINDOW_HEIGHT', () => {
- let oldHeight;
-
- beforeEach(() => {
- oldHeight = window.innerHeight;
- window.innerHeight = 700;
- });
+ afterEach(() => {
+ window.innerHeight = oldHeight;
+ });
- afterEach(() => {
- window.innerHeight = oldHeight;
- });
+ it('stays collapsed if changes are added or removed', async () => {
+ expect(findForm().exists()).toBe(false);
- it('stays collapsed when switching from edit view to commit view and back', async () => {
- store.state.currentActivityView = leftSidebarViews.edit.name;
- await vm.$nextTick();
+ store.state.stagedFiles = [];
+ await wrapper.vm.$nextTick();
- expect(vm.isCompact).toBe(true);
+ expect(findForm().exists()).toBe(false);
- store.state.currentActivityView = leftSidebarViews.commit.name;
- await vm.$nextTick();
+ store.state.stagedFiles.push('test');
+ await wrapper.vm.$nextTick();
- expect(vm.isCompact).toBe(true);
+ expect(findForm().exists()).toBe(false);
+ });
+ });
- store.state.currentActivityView = leftSidebarViews.edit.name;
- await vm.$nextTick();
+ describe('on commit tab', () => {
+ beforeEach(async () => {
+ // Test that the component reacts to switching to full view
+ goToEditView();
- expect(vm.isCompact).toBe(true);
- });
+ createComponent();
- it('stays uncollapsed if changes are added or removed', async () => {
- store.state.currentActivityView = leftSidebarViews.commit.name;
- await vm.$nextTick();
+ goToCommitView();
- expect(vm.isCompact).toBe(true);
+ await wrapper.vm.$nextTick();
+ });
- store.state.changedFiles = [];
- await vm.$nextTick();
+ it('shows form', () => {
+ expect(findForm().exists()).toBe(true);
+ });
- expect(vm.isCompact).toBe(true);
+ it('hides begin commit button', () => {
+ expect(findBeginCommitButton().exists()).toBe(false);
+ });
- store.state.changedFiles.push('test');
- await vm.$nextTick();
+ describe('when no changed files', () => {
+ beforeEach(async () => {
+ store.state.stagedFiles = [];
+ await wrapper.vm.$nextTick();
+ });
- expect(vm.isCompact).toBe(true);
+ it('hides form', () => {
+ expect(findForm().exists()).toBe(false);
});
- it('uncollapses when clicked on Commit button in the edit view', async () => {
- store.state.currentActivityView = leftSidebarViews.edit.name;
- beginCommitButton().click();
- await waitForPromises();
+ it('expands again when staged files are added', async () => {
+ store.state.stagedFiles.push('test');
+ await wrapper.vm.$nextTick();
- expect(vm.isCompact).toBe(false);
+ expect(findForm().exists()).toBe(true);
});
});
- });
- describe('full', () => {
- beforeEach(() => {
- vm.isCompact = false;
+ it('updates commitMessage in store on input', async () => {
+ setCommitMessageInput('testing commit message');
- return vm.$nextTick();
+ await wrapper.vm.$nextTick();
+
+ expect(store.state.commit.commitMessage).toBe('testing commit message');
});
- it('updates commitMessage in store on input', () => {
- const textarea = vm.$el.querySelector('textarea');
+ describe('discard draft button', () => {
+ it('hidden when commitMessage is empty', () => {
+ expect(findDiscardDraftButton().exists()).toBe(false);
+ });
- textarea.value = 'testing commit message';
+ it('resets commitMessage when clicking discard button', async () => {
+ setCommitMessageInput('testing commit message');
- textarea.dispatchEvent(new Event('input'));
+ await wrapper.vm.$nextTick();
- return vm.$nextTick().then(() => {
- expect(vm.$store.state.commit.commitMessage).toBe('testing commit message');
- });
- });
+ expect(findCommitMessageInput().props('text')).toBe('testing commit message');
- it('updating currentActivityView not to commit view sets compact mode', () => {
- store.state.currentActivityView = 'a';
+ // Test that commitMessage is cleared on click
+ findDiscardDraftButton().vm.$emit('click');
- return vm.$nextTick(() => {
- expect(vm.isCompact).toBe(true);
+ await wrapper.vm.$nextTick();
+
+ expect(findCommitMessageInput().props('text')).toBe('');
});
});
- it('always opens itself in full view current activity view is not commit view when clicking commit button', () => {
- beginCommitButton().click();
+ describe('when submitting', () => {
+ beforeEach(async () => {
+ goToEditView();
- return vm.$nextTick(() => {
- expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name);
- expect(vm.isCompact).toBe(false);
- });
- });
+ createComponent();
- describe('discard draft button', () => {
- it('hidden when commitMessage is empty', () => {
- expect(vm.$el.querySelector('.btn-default').textContent).toContain('Collapse');
- });
+ goToCommitView();
+
+ await wrapper.vm.$nextTick();
- it('resets commitMessage when clicking discard button', () => {
- vm.$store.state.commit.commitMessage = 'testing commit message';
-
- return vm
- .$nextTick()
- .then(() => {
- vm.$el.querySelector('.btn-default').click();
- })
- .then(() => vm.$nextTick())
- .then(() => {
- expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message');
- });
+ setCommitMessageInput('testing commit message');
+
+ await wrapper.vm.$nextTick();
+
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
});
- });
- describe('when submitting', () => {
- beforeEach(() => {
- jest.spyOn(vm, 'commitChanges');
+ it.each([clickCommitButton, submitForm])('when %p, commits changes', (fn) => {
+ fn();
- vm.$store.state.stagedFiles.push('test');
- vm.$store.state.commit.commitMessage = 'testing commit message';
+ expect(store.dispatch).toHaveBeenCalledWith('commit/commitChanges', undefined);
});
- it('calls commitChanges', () => {
- vm.commitChanges.mockResolvedValue({ success: true });
+ it('when cannot push code, submitting does nothing', async () => {
+ store.state.projects.abcproject.userPermissions.pushCode = false;
+ await wrapper.vm.$nextTick();
- return vm.$nextTick().then(() => {
- vm.$el.querySelector('.btn-success').click();
+ submitForm();
- expect(vm.commitChanges).toHaveBeenCalled();
- });
+ expect(store.dispatch).not.toHaveBeenCalled();
});
it.each`
@@ -272,31 +302,32 @@ describe('IDE commit form', () => {
${() => createCodeownersCommitError('test message')} | ${{ actionPrimary: { text: 'Create new branch' } }}
${createUnexpectedCommitError} | ${{ actionPrimary: null }}
`('opens error modal if commitError with $error', async ({ createError, props }) => {
- jest.spyOn(vm.$refs.commitErrorModal, 'show');
+ const modal = wrapper.find(GlModal);
+ modal.vm.show = jest.fn();
const error = createError();
store.state.commit.commitError = error;
- await vm.$nextTick();
+ await wrapper.vm.$nextTick();
- expect(vm.$refs.commitErrorModal.show).toHaveBeenCalled();
- expect(vm.$refs.commitErrorModal).toMatchObject({
+ expect(modal.vm.show).toHaveBeenCalled();
+ expect(modal.props()).toMatchObject({
actionCancel: { text: 'Cancel' },
...props,
});
// Because of the legacy 'mountComponent' approach here, the only way to
// test the text of the modal is by viewing the content of the modal added to the document.
- expect(document.body).toHaveText(error.messageHTML);
+ expect(modal.html()).toContain(error.messageHTML);
});
});
describe('with error modal with primary', () => {
beforeEach(() => {
- jest.spyOn(vm.$store, 'dispatch').mockReturnValue(Promise.resolve());
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
});
const commitActions = [
- ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH],
+ ['commit/updateCommitAction', COMMIT_TO_NEW_BRANCH],
['commit/commitChanges'],
];
@@ -310,27 +341,15 @@ describe('IDE commit form', () => {
async ({ commitError, expectedActions }) => {
store.state.commit.commitError = commitError('test message');
- await vm.$nextTick();
+ await wrapper.vm.$nextTick();
- getByText(document.body, 'Create new branch').click();
+ wrapper.find(GlModal).vm.$emit('ok');
await waitForPromises();
- expect(vm.$store.dispatch.mock.calls).toEqual(expectedActions);
+ expect(store.dispatch.mock.calls).toEqual(expectedActions);
},
);
});
});
-
- describe('commitButtonText', () => {
- it('returns commit text when staged files exist', () => {
- vm.$store.state.stagedFiles.push('testing');
-
- expect(vm.commitButtonText).toBe('Commit');
- });
-
- it('returns stage & commit text when staged files do not exist', () => {
- expect(vm.commitButtonText).toBe('Stage & Commit');
- });
- });
});
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 baa25a11c2a..b91ee88e0d6 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
import { trimText } from 'helpers/text_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
import { createRouter } from '~/ide/ide_router';
+import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list item', () => {
diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js
index 898ec4bebce..eb12fc994a5 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
+import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list', () => {
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 50da64abbbe..253c2a426ee 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
@@ -2,9 +2,12 @@ import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { projectData, branches } from 'jest/ide/mock_data';
import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
-import { createStore } from '~/ide/stores';
import { PERMISSION_CREATE_MR } from '~/ide/constants';
-import consts from '~/ide/stores/modules/commit/constants';
+import { createStore } from '~/ide/stores';
+import {
+ COMMIT_TO_CURRENT_BRANCH,
+ COMMIT_TO_NEW_BRANCH,
+} from '~/ide/stores/modules/commit/constants';
describe('create new MR checkbox', () => {
let store;
@@ -27,8 +30,8 @@ describe('create new MR checkbox', () => {
vm = createComponentWithStore(Component, store);
vm.$store.state.commit.commitAction = createNewBranch
- ? consts.COMMIT_TO_NEW_BRANCH
- : consts.COMMIT_TO_CURRENT_BRANCH;
+ ? COMMIT_TO_NEW_BRANCH
+ : COMMIT_TO_CURRENT_BRANCH;
vm.$store.state.currentBranchId = currentBranchId;
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 73d811f99b8..a6f3253321b 100644
--- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
+import { createStore } from '~/ide/stores';
describe('IDE commit sidebar radio group', () => {
let vm;
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 b116321efb3..7bbe47d37af 100644
--- a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import successMessage from '~/ide/components/commit_sidebar/success_message.vue';
+import { createStore } from '~/ide/stores';
describe('IDE commit panel successful commit state', () => {
let vm;
diff --git a/spec/frontend/ide/components/error_message_spec.js b/spec/frontend/ide/components/error_message_spec.js
index fa6816d3546..2de3fa863a8 100644
--- a/spec/frontend/ide/components/error_message_spec.js
+++ b/spec/frontend/ide/components/error_message_spec.js
@@ -1,6 +1,6 @@
+import { GlLoadingIcon } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
import ErrorMessage from '~/ide/components/error_message.vue';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/file_row_extra_spec.js b/spec/frontend/ide/components/file_row_extra_spec.js
index 275b98bc154..641407c7b77 100644
--- a/spec/frontend/ide/components/file_row_extra_spec.js
+++ b/spec/frontend/ide/components/file_row_extra_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import FileRowExtra from '~/ide/components/file_row_extra.vue';
+import { createStore } from '~/ide/stores';
import { file } from '../helpers';
describe('IDE extra file row component', () => {
diff --git a/spec/frontend/ide/components/file_templates/bar_spec.js b/spec/frontend/ide/components/file_templates/bar_spec.js
index 158995c9c9c..4ca99f8d055 100644
--- a/spec/frontend/ide/components/file_templates/bar_spec.js
+++ b/spec/frontend/ide/components/file_templates/bar_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import Bar from '~/ide/components/file_templates/bar.vue';
+import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
describe('IDE file templates bar component', () => {
diff --git a/spec/frontend/ide/components/file_templates/dropdown_spec.js b/spec/frontend/ide/components/file_templates/dropdown_spec.js
index 628580103a4..44ac9aa954d 100644
--- a/spec/frontend/ide/components/file_templates/dropdown_spec.js
+++ b/spec/frontend/ide/components/file_templates/dropdown_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
-import $ from 'jquery';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import $ from 'jquery';
+import Vuex from 'vuex';
import Dropdown from '~/ide/components/file_templates/dropdown.vue';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/ide_file_row_spec.js b/spec/frontend/ide/components/ide_file_row_spec.js
index c00ad7c8365..20c105460f2 100644
--- a/spec/frontend/ide/components/ide_file_row_spec.js
+++ b/spec/frontend/ide/components/ide_file_row_spec.js
@@ -1,9 +1,9 @@
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
-import IdeFileRow from '~/ide/components/ide_file_row.vue';
-import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowExtra from '~/ide/components/file_row_extra.vue';
+import IdeFileRow from '~/ide/components/ide_file_row.vue';
import { createStore } from '~/ide/stores';
+import FileRow from '~/vue_shared/components/file_row.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js
index 37f34a96495..740b7ada521 100644
--- a/spec/frontend/ide/components/ide_review_spec.js
+++ b/spec/frontend/ide/components/ide_review_spec.js
@@ -1,10 +1,10 @@
+import { createLocalVue, mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
-import { trimText } from 'helpers/text_helper';
import { keepAlive } from 'helpers/keep_alive_component_helper';
-import IdeReview from '~/ide/components/ide_review.vue';
+import { trimText } from 'helpers/text_helper';
import EditorModeDropdown from '~/ide/components/editor_mode_dropdown.vue';
+import IdeReview from '~/ide/components/ide_review.vue';
import { createStore } from '~/ide/stores';
import { file } from '../helpers';
import { projectData } from '../mock_data';
diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js
index 72e9463945b..c683612b142 100644
--- a/spec/frontend/ide/components/ide_side_bar_spec.js
+++ b/spec/frontend/ide/components/ide_side_bar_spec.js
@@ -1,13 +1,13 @@
+import { GlSkeletonLoading } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlSkeletonLoading } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
-import { createStore } from '~/ide/stores';
+import IdeReview from '~/ide/components/ide_review.vue';
import IdeSidebar from '~/ide/components/ide_side_bar.vue';
import IdeTree from '~/ide/components/ide_tree.vue';
import RepoCommitSection from '~/ide/components/repo_commit_section.vue';
-import IdeReview from '~/ide/components/ide_review.vue';
import { leftSidebarViews } from '~/ide/constants';
+import { createStore } from '~/ide/stores';
import { projectData } from '../mock_data';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/ide_sidebar_nav_spec.js b/spec/frontend/ide/components/ide_sidebar_nav_spec.js
index 6b4cb9bd03d..2ea0c250794 100644
--- a/spec/frontend/ide/components/ide_sidebar_nav_spec.js
+++ b/spec/frontend/ide/components/ide_sidebar_nav_spec.js
@@ -1,8 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import IdeSidebarNav from '~/ide/components/ide_sidebar_nav.vue';
import { SIDE_RIGHT, SIDE_LEFT } from '~/ide/constants';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
const TEST_TABS = [
{
@@ -74,7 +75,7 @@ describe('ide/components/ide_sidebar_nav', () => {
createComponent({ isOpen, side });
bsTooltipHide = jest.fn();
- wrapper.vm.$root.$on('bv::hide::tooltip', bsTooltipHide);
+ wrapper.vm.$root.$on(BV_HIDE_TOOLTIP, bsTooltipHide);
});
it('renders buttons', () => {
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index 805fa898611..c9d19c18d03 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -1,9 +1,10 @@
-import Vuex from 'vuex';
+import { GlAlert } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
-import { createStore } from '~/ide/stores';
import ErrorMessage from '~/ide/components/error_message.vue';
-import ide from '~/ide/components/ide.vue';
+import Ide from '~/ide/components/ide.vue';
+import { createStore } from '~/ide/stores';
import { file } from '../helpers';
import { projectData } from '../mock_data';
@@ -15,12 +16,12 @@ describe('WebIDE', () => {
let wrapper;
- function createComponent({ projData = emptyProjData, state = {} } = {}) {
+ const createComponent = ({ projData = emptyProjData, state = {} } = {}) => {
const store = createStore();
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
- store.state.projects.abcproject = { ...projData };
+ store.state.projects.abcproject = projData && { ...projData };
store.state.trees['abcproject/master'] = {
tree: [],
loading: false,
@@ -29,11 +30,13 @@ describe('WebIDE', () => {
store.state[key] = state[key];
});
- return shallowMount(ide, {
+ wrapper = shallowMount(Ide, {
store,
localVue,
});
- }
+ };
+
+ const findAlert = () => wrapper.find(GlAlert);
afterEach(() => {
wrapper.destroy();
@@ -42,7 +45,7 @@ describe('WebIDE', () => {
describe('ide component, empty repo', () => {
beforeEach(() => {
- wrapper = createComponent({
+ createComponent({
projData: {
empty_repo: true,
},
@@ -63,7 +66,7 @@ describe('WebIDE', () => {
`(
'should error message exists=$exists when errorMessage=$errorMessage',
async ({ errorMessage, exists }) => {
- wrapper = createComponent({
+ createComponent({
state: {
errorMessage,
},
@@ -78,12 +81,12 @@ describe('WebIDE', () => {
describe('onBeforeUnload', () => {
it('returns undefined when no staged files or changed files', () => {
- wrapper = createComponent();
+ createComponent();
expect(wrapper.vm.onBeforeUnload()).toBe(undefined);
});
it('returns warning text when their are changed files', () => {
- wrapper = createComponent({
+ createComponent({
state: {
changedFiles: [file()],
},
@@ -93,7 +96,7 @@ describe('WebIDE', () => {
});
it('returns warning text when their are staged files', () => {
- wrapper = createComponent({
+ createComponent({
state: {
stagedFiles: [file()],
},
@@ -104,7 +107,7 @@ describe('WebIDE', () => {
it('updates event object', () => {
const event = {};
- wrapper = createComponent({
+ createComponent({
state: {
stagedFiles: [file()],
},
@@ -118,7 +121,7 @@ describe('WebIDE', () => {
describe('non-existent branch', () => {
it('does not render "New file" button for non-existent branch when repo is not empty', () => {
- wrapper = createComponent({
+ createComponent({
state: {
projects: {},
},
@@ -130,7 +133,7 @@ describe('WebIDE', () => {
describe('branch with files', () => {
beforeEach(() => {
- wrapper = createComponent({
+ createComponent({
projData: {
empty_repo: false,
},
@@ -142,4 +145,31 @@ describe('WebIDE', () => {
});
});
});
+
+ it('when user cannot push code, shows alert', () => {
+ createComponent({
+ projData: {
+ userPermissions: {
+ pushCode: false,
+ },
+ },
+ });
+
+ expect(findAlert().props()).toMatchObject({
+ dismissible: false,
+ });
+ expect(findAlert().text()).toBe(Ide.MSG_CANNOT_PUSH_CODE);
+ });
+
+ it.each`
+ desc | projData
+ ${'when user can push code'} | ${{ userPermissions: { pushCode: true } }}
+ ${'when project is not ready'} | ${null}
+ `('$desc, no alert is shown', ({ projData }) => {
+ createComponent({
+ projData,
+ });
+
+ expect(findAlert().exists()).toBe(false);
+ });
});
diff --git a/spec/frontend/ide/components/ide_status_bar_spec.js b/spec/frontend/ide/components/ide_status_bar_spec.js
index 47506bb87e8..9d33a1e2554 100644
--- a/spec/frontend/ide/components/ide_status_bar_spec.js
+++ b/spec/frontend/ide/components/ide_status_bar_spec.js
@@ -1,10 +1,10 @@
-import Vue from 'vue';
import _ from 'lodash';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
-import { createStore } from '~/ide/stores';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import IdeStatusBar from '~/ide/components/ide_status_bar.vue';
import { rightSidebarViews } from '~/ide/constants';
+import { createStore } from '~/ide/stores';
import { projectData } from '../mock_data';
const TEST_PROJECT_ID = 'abcproject';
diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js
index 02b5dc19bd8..036edfb3ec1 100644
--- a/spec/frontend/ide/components/ide_status_list_spec.js
+++ b/spec/frontend/ide/components/ide_status_list_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import IdeStatusList from '~/ide/components/ide_status_list.vue';
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
diff --git a/spec/frontend/ide/components/ide_status_mr_spec.js b/spec/frontend/ide/components/ide_status_mr_spec.js
index ac1be4b21c0..0526d4653f8 100644
--- a/spec/frontend/ide/components/ide_status_mr_spec.js
+++ b/spec/frontend/ide/components/ide_status_mr_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import IdeStatusMr from '~/ide/components/ide_status_mr.vue';
diff --git a/spec/frontend/ide/components/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js
index c5934b032c4..6eef646b012 100644
--- a/spec/frontend/ide/components/ide_tree_spec.js
+++ b/spec/frontend/ide/components/ide_tree_spec.js
@@ -1,6 +1,6 @@
+import { mount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { keepAlive } from 'helpers/keep_alive_component_helper';
import IdeTree from '~/ide/components/ide_tree.vue';
import { createStore } from '~/ide/stores';
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 b323ad8320c..d632a34266a 100644
--- a/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js
+++ b/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue';
describe('IDE job log scroll button', () => {
diff --git a/spec/frontend/ide/components/jobs/list_spec.js b/spec/frontend/ide/components/jobs/list_spec.js
index 5c5c6602374..8797e07aef1 100644
--- a/spec/frontend/ide/components/jobs/list_spec.js
+++ b/spec/frontend/ide/components/jobs/list_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import StageList from '~/ide/components/jobs/list.vue';
import Stage from '~/ide/components/jobs/stage.vue';
diff --git a/spec/frontend/ide/components/jobs/stage_spec.js b/spec/frontend/ide/components/jobs/stage_spec.js
index e80215b9d13..9accd81a2ba 100644
--- a/spec/frontend/ide/components/jobs/stage_spec.js
+++ b/spec/frontend/ide/components/jobs/stage_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
-import Stage from '~/ide/components/jobs/stage.vue';
+import { shallowMount } from '@vue/test-utils';
import Item from '~/ide/components/jobs/item.vue';
+import Stage from '~/ide/components/jobs/stage.vue';
import { stages, jobs } from '../../mock_data';
describe('IDE pipeline stage', () => {
diff --git a/spec/frontend/ide/components/merge_requests/item_spec.js b/spec/frontend/ide/components/merge_requests/item_spec.js
index 20adaa7abbc..f0a97a0b10a 100644
--- a/spec/frontend/ide/components/merge_requests/item_spec.js
+++ b/spec/frontend/ide/components/merge_requests/item_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
-import { createStore } from '~/ide/stores';
-import { createRouter } from '~/ide/ide_router';
+import Vuex from 'vuex';
import Item from '~/ide/components/merge_requests/item.vue';
+import { createRouter } from '~/ide/ide_router';
+import { createStore } from '~/ide/stores';
const TEST_ITEM = {
iid: 1,
diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js
index f0ac852fa67..85acabca38b 100644
--- a/spec/frontend/ide/components/merge_requests/list_spec.js
+++ b/spec/frontend/ide/components/merge_requests/list_spec.js
@@ -1,8 +1,8 @@
+import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
-import List from '~/ide/components/merge_requests/list.vue';
import Item from '~/ide/components/merge_requests/item.vue';
+import List from '~/ide/components/merge_requests/list.vue';
import TokenedInput from '~/ide/components/shared/tokened_input.vue';
import { mergeRequests as mergeRequestsMock } from '../../mock_data';
diff --git a/spec/frontend/ide/components/nav_dropdown_spec.js b/spec/frontend/ide/components/nav_dropdown_spec.js
index 12a1a4c8013..4ddb3930764 100644
--- a/spec/frontend/ide/components/nav_dropdown_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_spec.js
@@ -1,8 +1,8 @@
-import $ from 'jquery';
import { mount } from '@vue/test-utils';
-import { createStore } from '~/ide/stores';
+import $ from 'jquery';
import NavDropdown from '~/ide/components/nav_dropdown.vue';
import { PERMISSION_READ_MR } from '~/ide/constants';
+import { createStore } from '~/ide/stores';
const TEST_PROJECT_ID = 'lorem-ipsum';
diff --git a/spec/frontend/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js
index 793d950b3e0..5a1c0471206 100644
--- a/spec/frontend/ide/components/new_dropdown/index_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/index_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
import newDropdown from '~/ide/components/new_dropdown/index.vue';
+import { createStore } from '~/ide/stores';
describe('new dropdown component', () => {
let store;
diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js
index 06434081e39..0600fcea917 100644
--- a/spec/frontend/ide/components/new_dropdown/modal_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
-import modal from '~/ide/components/new_dropdown/modal.vue';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import modal from '~/ide/components/new_dropdown/modal.vue';
+import { createStore } from '~/ide/stores';
jest.mock('~/flash');
diff --git a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
index c3da2a46858..7216f50b05c 100644
--- a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
+++ b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
@@ -1,9 +1,9 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
+import IdeSidebarNav from '~/ide/components/ide_sidebar_nav.vue';
+import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
import { createStore } from '~/ide/stores';
import paneModule from '~/ide/stores/modules/pane';
-import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
-import IdeSidebarNav from '~/ide/components/ide_sidebar_nav.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/ide/components/panes/right_spec.js b/spec/frontend/ide/components/panes/right_spec.js
index 57fd0e49e94..c6231d129ff 100644
--- a/spec/frontend/ide/components/panes/right_spec.js
+++ b/spec/frontend/ide/components/panes/right_spec.js
@@ -1,10 +1,10 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { createStore } from '~/ide/stores';
-import RightPane from '~/ide/components/panes/right.vue';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
+import RightPane from '~/ide/components/panes/right.vue';
import { rightSidebarViews } from '~/ide/constants';
+import { createStore } from '~/ide/stores';
import extendStore from '~/ide/stores/extend';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js
index a1fbfd96c31..58d8c0629fb 100644
--- a/spec/frontend/ide/components/pipelines/list_spec.js
+++ b/spec/frontend/ide/components/pipelines/list_spec.js
@@ -1,13 +1,13 @@
+import { GlLoadingIcon, GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { GlLoadingIcon, GlTab } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import { pipelines } from 'jest/ide/mock_data';
-import List from '~/ide/components/pipelines/list.vue';
import JobsList from '~/ide/components/jobs/list.vue';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import List from '~/ide/components/pipelines/list.vue';
import IDEServices from '~/ide/services';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
Vue.use(Vuex);
diff --git a/spec/frontend/ide/components/preview/clientside_spec.js b/spec/frontend/ide/components/preview/clientside_spec.js
index 220982e1fd9..1768f01f3b8 100644
--- a/spec/frontend/ide/components/preview/clientside_spec.js
+++ b/spec/frontend/ide/components/preview/clientside_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import smooshpack from 'smooshpack';
+import Vuex from 'vuex';
import Clientside from '~/ide/components/preview/clientside.vue';
import eventHub from '~/ide/eventhub';
diff --git a/spec/frontend/ide/components/preview/navigator_spec.js b/spec/frontend/ide/components/preview/navigator_spec.js
index ba5ac3bbbea..ee760364c7e 100644
--- a/spec/frontend/ide/components/preview/navigator_spec.js
+++ b/spec/frontend/ide/components/preview/navigator_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
-import { TEST_HOST } from 'helpers/test_constants';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { listen } from 'codesandbox-api';
+import { TEST_HOST } from 'helpers/test_constants';
import ClientsideNavigator from '~/ide/components/preview/navigator.vue';
jest.mock('codesandbox-api', () => ({
diff --git a/spec/frontend/ide/components/repo_commit_section_spec.js b/spec/frontend/ide/components/repo_commit_section_spec.js
index e91debee4ca..c174f5e2006 100644
--- a/spec/frontend/ide/components/repo_commit_section_spec.js
+++ b/spec/frontend/ide/components/repo_commit_section_spec.js
@@ -1,10 +1,10 @@
import { mount } from '@vue/test-utils';
import { keepAlive } from 'helpers/keep_alive_component_helper';
-import { createStore } from '~/ide/stores';
-import { createRouter } from '~/ide/ide_router';
-import RepoCommitSection from '~/ide/components/repo_commit_section.vue';
import EmptyState from '~/ide/components/commit_sidebar/empty_state.vue';
+import RepoCommitSection from '~/ide/components/repo_commit_section.vue';
import { stageKeys } from '~/ide/constants';
+import { createRouter } from '~/ide/ide_router';
+import { createStore } from '~/ide/stores';
import { file } from '../helpers';
const TEST_NO_CHANGES_SVG = 'nochangessvg';
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index 89a7f423e34..1985feb1615 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -1,22 +1,22 @@
-import Vuex from 'vuex';
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import '~/behaviors/markdown/render_gfm';
import { Range } from 'monaco-editor';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import '~/behaviors/markdown/render_gfm';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
import waitUsingRealTimer from 'helpers/wait_using_real_timer';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import axios from '~/lib/utils/axios_utils';
-import service from '~/ide/services';
-import { createStoreOptions } from '~/ide/stores';
import RepoEditor from '~/ide/components/repo_editor.vue';
-import Editor from '~/ide/lib/editor';
import {
leftSidebarViews,
FILE_VIEW_MODE_EDITOR,
FILE_VIEW_MODE_PREVIEW,
viewerTypes,
} from '~/ide/constants';
+import Editor from '~/ide/lib/editor';
+import service from '~/ide/services';
+import { createStoreOptions } from '~/ide/stores';
+import axios from '~/lib/utils/axios_utils';
import { file } from '../helpers';
import { exampleConfigs, exampleFiles } from '../lib/editorconfig/mock_data';
diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js
index a44c8b4d5ee..b39a488b034 100644
--- a/spec/frontend/ide/components/repo_tab_spec.js
+++ b/spec/frontend/ide/components/repo_tab_spec.js
@@ -1,8 +1,8 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { createStore } from '~/ide/stores';
import RepoTab from '~/ide/components/repo_tab.vue';
import { createRouter } from '~/ide/ide_router';
+import { createStore } from '~/ide/stores';
import { file } from '../helpers';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/repo_tabs_spec.js b/spec/frontend/ide/components/repo_tabs_spec.js
index 45a17c37667..6ee73b0a437 100644
--- a/spec/frontend/ide/components/repo_tabs_spec.js
+++ b/spec/frontend/ide/components/repo_tabs_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
-import { createStore } from '~/ide/stores';
+import Vuex from 'vuex';
import RepoTabs from '~/ide/components/repo_tabs.vue';
+import { createStore } from '~/ide/stores';
import { file } from '../helpers';
const localVue = createLocalVue();
diff --git a/spec/frontend/ide/components/resizable_panel_spec.js b/spec/frontend/ide/components/resizable_panel_spec.js
index b1a1212371e..6a5af52ea35 100644
--- a/spec/frontend/ide/components/resizable_panel_spec.js
+++ b/spec/frontend/ide/components/resizable_panel_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import ResizablePanel from '~/ide/components/resizable_panel.vue';
-import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import { SIDE_LEFT, SIDE_RIGHT } from '~/ide/constants';
+import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
const TEST_WIDTH = 500;
const TEST_MIN_WIDTH = 400;
diff --git a/spec/frontend/ide/components/terminal/empty_state_spec.js b/spec/frontend/ide/components/terminal/empty_state_spec.js
index b62470f67b6..57c816747aa 100644
--- a/spec/frontend/ide/components/terminal/empty_state_spec.js
+++ b/spec/frontend/ide/components/terminal/empty_state_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue';
diff --git a/spec/frontend/ide/components/terminal/session_spec.js b/spec/frontend/ide/components/terminal/session_spec.js
index 5653c8bf14d..5659a7d15da 100644
--- a/spec/frontend/ide/components/terminal/session_spec.js
+++ b/spec/frontend/ide/components/terminal/session_spec.js
@@ -1,5 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import TerminalSession from '~/ide/components/terminal/session.vue';
import Terminal from '~/ide/components/terminal/terminal.vue';
diff --git a/spec/frontend/ide/components/terminal/terminal_controls_spec.js b/spec/frontend/ide/components/terminal/terminal_controls_spec.js
index 99182710218..416096083f0 100644
--- a/spec/frontend/ide/components/terminal/terminal_controls_spec.js
+++ b/spec/frontend/ide/components/terminal/terminal_controls_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import TerminalControls from '~/ide/components/terminal/terminal_controls.vue';
import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue';
+import TerminalControls from '~/ide/components/terminal/terminal_controls.vue';
describe('IDE TerminalControls', () => {
let wrapper;
diff --git a/spec/frontend/ide/components/terminal/terminal_spec.js b/spec/frontend/ide/components/terminal/terminal_spec.js
index 9cfe167d2f6..c4b186c004a 100644
--- a/spec/frontend/ide/components/terminal/terminal_spec.js
+++ b/spec/frontend/ide/components/terminal/terminal_spec.js
@@ -1,7 +1,7 @@
-import Vue, { nextTick } from 'vue';
+import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
import Terminal from '~/ide/components/terminal/terminal.vue';
import TerminalControls from '~/ide/components/terminal/terminal_controls.vue';
import {
diff --git a/spec/frontend/ide/components/terminal/view_spec.js b/spec/frontend/ide/components/terminal/view_spec.js
index 37f7957c526..e97d4d8a73b 100644
--- a/spec/frontend/ide/components/terminal/view_spec.js
+++ b/spec/frontend/ide/components/terminal/view_spec.js
@@ -1,10 +1,10 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { TEST_HOST } from 'spec/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue';
-import TerminalView from '~/ide/components/terminal/view.vue';
import TerminalSession from '~/ide/components/terminal/session.vue';
+import TerminalView from '~/ide/components/terminal/view.vue';
const TEST_HELP_PATH = `${TEST_HOST}/help`;
const TEST_SVG_PATH = `${TEST_HOST}/illustration.svg`;
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 afdecb7bbbd..69077ef2c68 100644
--- a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js
+++ b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import TerminalSyncStatus from '~/ide/components/terminal_sync/terminal_sync_status.vue';
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
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 d15583f81e4..c916c43d1e2 100644
--- a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js
+++ b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import TerminalSyncStatus from '~/ide/components/terminal_sync/terminal_sync_status.vue';
import {
MSG_TERMINAL_SYNC_CONNECTING,
diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js
index f815c3d090e..9b7a4715d7f 100644
--- a/spec/frontend/ide/helpers.js
+++ b/spec/frontend/ide/helpers.js
@@ -1,6 +1,6 @@
import * as pathUtils from 'path';
-import { decorateData } from '~/ide/stores/utils';
import { commitActionTypes } from '~/ide/constants';
+import { decorateData } from '~/ide/stores/utils';
export const file = (name = 'name', id = name, type = '', parent = null) =>
decorateData({
diff --git a/spec/frontend/ide/lib/create_diff_spec.js b/spec/frontend/ide/lib/create_diff_spec.js
index 76494f9af1b..b33fa599d1c 100644
--- a/spec/frontend/ide/lib/create_diff_spec.js
+++ b/spec/frontend/ide/lib/create_diff_spec.js
@@ -1,6 +1,6 @@
+import { commitActionTypes } from '~/ide/constants';
import createDiff from '~/ide/lib/create_diff';
import createFileDiff from '~/ide/lib/create_file_diff';
-import { commitActionTypes } from '~/ide/constants';
import {
createNewFile,
createUpdatedFile,
diff --git a/spec/frontend/ide/lib/create_file_diff_spec.js b/spec/frontend/ide/lib/create_file_diff_spec.js
index 294f0a926aa..646304c431b 100644
--- a/spec/frontend/ide/lib/create_file_diff_spec.js
+++ b/spec/frontend/ide/lib/create_file_diff_spec.js
@@ -1,5 +1,5 @@
-import createFileDiff from '~/ide/lib/create_file_diff';
import { commitActionTypes } from '~/ide/constants';
+import createFileDiff from '~/ide/lib/create_file_diff';
import {
createUpdatedFile,
createNewFile,
diff --git a/spec/frontend/ide/lib/decorations/controller_spec.js b/spec/frontend/ide/lib/decorations/controller_spec.js
index e9b7faaadfe..b513f1b2eba 100644
--- a/spec/frontend/ide/lib/decorations/controller_spec.js
+++ b/spec/frontend/ide/lib/decorations/controller_spec.js
@@ -1,8 +1,8 @@
-import Editor from '~/ide/lib/editor';
-import DecorationsController from '~/ide/lib/decorations/controller';
import Model from '~/ide/lib/common/model';
-import { file } from '../../helpers';
+import DecorationsController from '~/ide/lib/decorations/controller';
+import Editor from '~/ide/lib/editor';
import { createStore } from '~/ide/stores';
+import { file } from '../../helpers';
describe('Multi-file editor library decorations controller', () => {
let editorInstance;
diff --git a/spec/frontend/ide/lib/diff/controller_spec.js b/spec/frontend/ide/lib/diff/controller_spec.js
index 57c134620c0..5f1344f1ea2 100644
--- a/spec/frontend/ide/lib/diff/controller_spec.js
+++ b/spec/frontend/ide/lib/diff/controller_spec.js
@@ -1,9 +1,9 @@
import { Range } from 'monaco-editor';
-import Editor from '~/ide/lib/editor';
import ModelManager from '~/ide/lib/common/model_manager';
import DecorationsController from '~/ide/lib/decorations/controller';
import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
import { computeDiff } from '~/ide/lib/diff/diff';
+import Editor from '~/ide/lib/editor';
import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
diff --git a/spec/frontend/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js
index 12779c61dc3..c21a7edb2da 100644
--- a/spec/frontend/ide/lib/editor_spec.js
+++ b/spec/frontend/ide/lib/editor_spec.js
@@ -4,9 +4,10 @@ import {
Range,
Selection,
} from 'monaco-editor';
+import { EDITOR_TYPE_DIFF } from '~/editor/constants';
import Editor from '~/ide/lib/editor';
-import { createStore } from '~/ide/stores';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
+import { createStore } from '~/ide/stores';
import { file } from '../helpers';
describe('Multi-file editor library', () => {
@@ -125,7 +126,7 @@ describe('Multi-file editor library', () => {
});
it('sets original & modified when diff editor', () => {
- jest.spyOn(instance.instance, 'getEditorType').mockReturnValue('vs.editor.IDiffEditor');
+ jest.spyOn(instance.instance, 'getEditorType').mockReturnValue(EDITOR_TYPE_DIFF);
jest.spyOn(instance.instance, 'setModel').mockImplementation(() => {});
instance.attachModel(model);
diff --git a/spec/frontend/ide/lib/languages/hcl_spec.js b/spec/frontend/ide/lib/languages/hcl_spec.js
index c6ebad6a4f4..948c44d4543 100644
--- a/spec/frontend/ide/lib/languages/hcl_spec.js
+++ b/spec/frontend/ide/lib/languages/hcl_spec.js
@@ -1,6 +1,6 @@
import { editor } from 'monaco-editor';
-import { registerLanguages } from '~/ide/utils';
import hcl from '~/ide/lib/languages/hcl';
+import { registerLanguages } from '~/ide/utils';
describe('tokenization for .tf files', () => {
beforeEach(() => {
diff --git a/spec/frontend/ide/lib/languages/vue_spec.js b/spec/frontend/ide/lib/languages/vue_spec.js
index ba5c31bb101..e3a67ccd47f 100644
--- a/spec/frontend/ide/lib/languages/vue_spec.js
+++ b/spec/frontend/ide/lib/languages/vue_spec.js
@@ -1,6 +1,6 @@
import { editor } from 'monaco-editor';
-import { registerLanguages } from '~/ide/utils';
import vue from '~/ide/lib/languages/vue';
+import { registerLanguages } from '~/ide/utils';
// This file only tests syntax specific to vue. This does not test existing syntaxes
// of html, javascript, css and handlebars, which vue files extend.
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index c3d6182bd78..678d58cba34 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -1,10 +1,10 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import services from '~/ide/services';
import Api from '~/api';
+import getUserPermissions from '~/ide/queries/getUserPermissions.query.graphql';
+import services from '~/ide/services';
import { query } from '~/ide/services/gql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
-import getUserPermissions from '~/ide/queries/getUserPermissions.query.graphql';
import { projectData } from '../mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 9d367714bbe..6178fb08d8c 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
+import Vue from 'vue';
+import eventHub from '~/ide/eventhub';
+import { createRouter } from '~/ide/ide_router';
+import service from '~/ide/services';
import { createStore } from '~/ide/stores';
import * as actions from '~/ide/stores/actions/file';
import * as types from '~/ide/stores/mutation_types';
-import service from '~/ide/services';
-import { createRouter } from '~/ide/ide_router';
-import eventHub from '~/ide/eventhub';
+import axios from '~/lib/utils/axios_utils';
import { file, createTriggerRenameAction, createTriggerUpdatePayload } from '../../helpers';
const ORIGINAL_CONTENT = 'original content';
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index 9b17d95ea35..600bd5fe9e1 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -1,19 +1,33 @@
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import { createStore } from '~/ide/stores';
+import { range } from 'lodash';
+import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { leftSidebarViews, PERMISSION_READ_MR, MAX_MR_FILES_AUTO_OPEN } from '~/ide/constants';
+import service from '~/ide/services';
+import { createStore } from '~/ide/stores';
import {
getMergeRequestData,
getMergeRequestChanges,
getMergeRequestVersions,
+ openMergeRequestChanges,
openMergeRequest,
} from '~/ide/stores/actions/merge_request';
-import service from '~/ide/services';
-import { leftSidebarViews, PERMISSION_READ_MR } from '~/ide/constants';
+import * as types from '~/ide/stores/mutation_types';
+import axios from '~/lib/utils/axios_utils';
const TEST_PROJECT = 'abcproject';
const TEST_PROJECT_ID = 17;
+const createMergeRequestChange = (path) => ({
+ new_path: path,
+ path,
+});
+const createMergeRequestChangesCount = (n) =>
+ range(n).map((i) => createMergeRequestChange(`loremispum_${i}.md`));
+
+const testGetUrlForPath = (path) => `${TEST_HOST}/test/${path}`;
+
jest.mock('~/flash');
describe('IDE store merge request actions', () => {
@@ -353,6 +367,72 @@ describe('IDE store merge request actions', () => {
});
});
+ describe('openMergeRequestChanges', () => {
+ it.each`
+ desc | changes | entries
+ ${'with empty changes'} | ${[]} | ${{}}
+ ${'with changes not matching entries'} | ${[{ new_path: '123.md' }]} | ${{ '456.md': {} }}
+ `('$desc, does nothing', ({ changes, entries }) => {
+ const state = { entries };
+
+ return testAction({
+ action: openMergeRequestChanges,
+ state,
+ payload: changes,
+ expectedActions: [],
+ expectedMutations: [],
+ });
+ });
+
+ it('updates views and opens mr changes', () => {
+ // This is the payload sent to the action
+ const changesPayload = createMergeRequestChangesCount(15);
+
+ // Remove some items from the payload to use for entries
+ const changes = changesPayload.slice(1, 14);
+
+ const entries = changes.reduce(
+ (acc, { path }) => Object.assign(acc, { [path]: path, type: 'blob' }),
+ {},
+ );
+ const pathsToOpen = changes.slice(0, MAX_MR_FILES_AUTO_OPEN).map((x) => x.new_path);
+
+ return testAction({
+ action: openMergeRequestChanges,
+ state: { entries, getUrlForPath: testGetUrlForPath },
+ payload: changesPayload,
+ expectedActions: [
+ { type: 'updateActivityBarView', payload: leftSidebarViews.review.name },
+ // Only activates first file
+ { type: 'router/push', payload: testGetUrlForPath(pathsToOpen[0]) },
+ { type: 'setFileActive', payload: pathsToOpen[0] },
+ // Fetches data for other files
+ ...pathsToOpen.slice(1).map((path) => ({
+ type: 'getFileData',
+ payload: { path, makeFileActive: false },
+ })),
+ ...pathsToOpen.slice(1).map((path) => ({
+ type: 'getRawFileData',
+ payload: { path },
+ })),
+ ],
+ expectedMutations: [
+ ...changes.map((change) => ({
+ type: types.SET_FILE_MERGE_REQUEST_CHANGE,
+ payload: {
+ file: entries[change.new_path],
+ mrChange: change,
+ },
+ })),
+ ...pathsToOpen.map((path) => ({
+ type: types.TOGGLE_FILE_OPEN,
+ payload: path,
+ })),
+ ],
+ });
+ });
+ });
+
describe('openMergeRequest', () => {
const mr = {
projectId: TEST_PROJECT,
@@ -409,7 +489,6 @@ describe('IDE store merge request actions', () => {
case 'getFiles':
case 'getMergeRequestVersions':
case 'getBranchData':
- case 'setFileMrChange':
return Promise.resolve();
default:
return originalDispatch(type, payload);
@@ -445,6 +524,7 @@ describe('IDE store merge request actions', () => {
],
['getMergeRequestVersions', mr],
['getMergeRequestChanges', mr],
+ ['openMergeRequestChanges', testMergeRequestChanges.changes],
]);
})
.then(done)
@@ -454,9 +534,11 @@ describe('IDE store merge request actions', () => {
it('updates activity bar view and gets file data, if changes are found', (done) => {
store.state.entries.foo = {
type: 'blob',
+ path: 'foo',
};
store.state.entries.bar = {
type: 'blob',
+ path: 'bar',
};
testMergeRequestChanges.changes = [
@@ -467,24 +549,9 @@ describe('IDE store merge request actions', () => {
openMergeRequest({ state: store.state, dispatch: store.dispatch, getters: mockGetters }, mr)
.then(() => {
expect(store.dispatch).toHaveBeenCalledWith(
- 'updateActivityBarView',
- leftSidebarViews.review.name,
+ 'openMergeRequestChanges',
+ testMergeRequestChanges.changes,
);
-
- testMergeRequestChanges.changes.forEach((change, i) => {
- expect(store.dispatch).toHaveBeenCalledWith('setFileMrChange', {
- file: store.state.entries[change.new_path],
- mrChange: change,
- });
-
- expect(store.dispatch).toHaveBeenCalledWith('getFileData', {
- path: change.new_path,
- makeFileActive: i === 0,
- openFile: true,
- });
- });
-
- expect(store.state.openFiles.length).toBe(testMergeRequestChanges.changes.length);
})
.then(done)
.catch(done.fail);
diff --git a/spec/frontend/ide/stores/actions/project_spec.js b/spec/frontend/ide/stores/actions/project_spec.js
index 1244c8af91a..23ffb5ff56b 100644
--- a/spec/frontend/ide/stores/actions/project_spec.js
+++ b/spec/frontend/ide/stores/actions/project_spec.js
@@ -1,7 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
-import axios from '~/lib/utils/axios_utils';
+import testAction from 'helpers/vuex_action_helper';
+import api from '~/api';
+import service from '~/ide/services';
import { createStore } from '~/ide/stores';
import {
refreshLastCommitData,
@@ -12,8 +13,7 @@ import {
loadFile,
loadBranch,
} from '~/ide/stores/actions';
-import service from '~/ide/services';
-import api from '~/api';
+import axios from '~/lib/utils/axios_utils';
const TEST_PROJECT_ID = 'abc/def';
diff --git a/spec/frontend/ide/stores/actions/tree_spec.js b/spec/frontend/ide/stores/actions/tree_spec.js
index 7831df9f3a4..8de2188a5f4 100644
--- a/spec/frontend/ide/stores/actions/tree_spec.js
+++ b/spec/frontend/ide/stores/actions/tree_spec.js
@@ -1,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
+import { createRouter } from '~/ide/ide_router';
+import service from '~/ide/services';
+import { createStore } from '~/ide/stores';
import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree';
import * as types from '~/ide/stores/mutation_types';
import axios from '~/lib/utils/axios_utils';
-import { createStore } from '~/ide/stores';
-import service from '~/ide/services';
-import { createRouter } from '~/ide/ide_router';
import { file, createEntriesFromPaths } from '../../helpers';
describe('Multi-file store tree actions', () => {
diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js
index 036bc91cd11..d47dd88dd47 100644
--- a/spec/frontend/ide/stores/actions_spec.js
+++ b/spec/frontend/ide/stores/actions_spec.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { visitUrl } from '~/lib/utils/url_utility';
-import { createStore } from '~/ide/stores';
+import eventHub from '~/ide/eventhub';
import { createRouter } from '~/ide/ide_router';
+import { createStore } from '~/ide/stores';
import {
stageAllChanges,
unstageAllChanges,
@@ -18,10 +18,10 @@ import {
createTempEntry,
discardAllChanges,
} from '~/ide/stores/actions';
-import axios from '~/lib/utils/axios_utils';
import * as types from '~/ide/stores/mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
import { file, createTriggerRenameAction, createTriggerChangeAction } from '../helpers';
-import eventHub from '~/ide/eventhub';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js
index 1787f9e9361..450f5592026 100644
--- a/spec/frontend/ide/stores/getters_spec.js
+++ b/spec/frontend/ide/stores/getters_spec.js
@@ -1,6 +1,7 @@
import { TEST_HOST } from 'helpers/test_constants';
-import * as getters from '~/ide/stores/getters';
import { createStore } from '~/ide/stores';
+import * as getters from '~/ide/stores/getters';
+import { DEFAULT_PERMISSIONS } from '../../../../app/assets/javascripts/ide/constants';
import { file } from '../helpers';
const TEST_PROJECT_ID = 'test_project';
@@ -386,7 +387,9 @@ describe('IDE store getters', () => {
describe('findProjectPermissions', () => {
it('returns false if project not found', () => {
- expect(localStore.getters.findProjectPermissions(TEST_PROJECT_ID)).toEqual({});
+ expect(localStore.getters.findProjectPermissions(TEST_PROJECT_ID)).toEqual(
+ DEFAULT_PERMISSIONS,
+ );
});
it('finds permission in given project', () => {
diff --git a/spec/frontend/ide/stores/modules/branches/actions_spec.js b/spec/frontend/ide/stores/modules/branches/actions_spec.js
index b1c077c4082..135dbc1f746 100644
--- a/spec/frontend/ide/stores/modules/branches/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/branches/actions_spec.js
@@ -1,8 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import state from '~/ide/stores/modules/branches/state';
-import * as types from '~/ide/stores/modules/branches/mutation_types';
import {
requestBranches,
receiveBranchesError,
@@ -10,6 +7,9 @@ import {
fetchBranches,
resetBranches,
} from '~/ide/stores/modules/branches/actions';
+import * as types from '~/ide/stores/modules/branches/mutation_types';
+import state from '~/ide/stores/modules/branches/state';
+import axios from '~/lib/utils/axios_utils';
import { branches, projectData } from '../../../mock_data';
describe('IDE branches actions', () => {
diff --git a/spec/frontend/ide/stores/modules/branches/mutations_spec.js b/spec/frontend/ide/stores/modules/branches/mutations_spec.js
index ddf55479be9..fd6006749d2 100644
--- a/spec/frontend/ide/stores/modules/branches/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/branches/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/ide/stores/modules/branches/state';
-import mutations from '~/ide/stores/modules/branches/mutations';
import * as types from '~/ide/stores/modules/branches/mutation_types';
+import mutations from '~/ide/stores/modules/branches/mutations';
+import state from '~/ide/stores/modules/branches/state';
import { branches } from '../../../mock_data';
describe('IDE branches mutations', () => {
diff --git a/spec/frontend/ide/stores/modules/clientside/actions_spec.js b/spec/frontend/ide/stores/modules/clientside/actions_spec.js
index 05627f8ed0e..c167d056039 100644
--- a/spec/frontend/ide/stores/modules/clientside/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/clientside/actions_spec.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
+import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/ide/stores/modules/clientside/actions';
+import axios from '~/lib/utils/axios_utils';
const TEST_PROJECT_URL = `${TEST_HOST}/lorem/ipsum`;
const TEST_USAGE_URL = `${TEST_PROJECT_URL}/usage_ping/web_ide_clientside_preview`;
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index 5be0e22a9fc..b124eb391f3 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -1,17 +1,20 @@
-import { file } from 'jest/ide/helpers';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { visitUrl } from '~/lib/utils/url_utility';
-import { createStore } from '~/ide/stores';
-import service from '~/ide/services';
-import { createRouter } from '~/ide/ide_router';
+import { file } from 'jest/ide/helpers';
+import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants';
import eventHub from '~/ide/eventhub';
-import consts from '~/ide/stores/modules/commit/constants';
-import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
-import * as actions from '~/ide/stores/modules/commit/actions';
+import { createRouter } from '~/ide/ide_router';
import { createUnexpectedCommitError } from '~/ide/lib/errors';
-import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants';
+import service from '~/ide/services';
+import { createStore } from '~/ide/stores';
+import * as actions from '~/ide/stores/modules/commit/actions';
+import {
+ COMMIT_TO_CURRENT_BRANCH,
+ COMMIT_TO_NEW_BRANCH,
+} from '~/ide/stores/modules/commit/constants';
+import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
+import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -425,12 +428,12 @@ describe('IDE commit module actions', () => {
});
it('resets stores commit actions', (done) => {
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
store
.dispatch('commit/commitChanges')
.then(() => {
- expect(store.state.commit.commitAction).not.toBe(consts.COMMIT_TO_NEW_BRANCH);
+ expect(store.state.commit.commitAction).not.toBe(COMMIT_TO_NEW_BRANCH);
})
.then(done)
.catch(done.fail);
@@ -450,7 +453,7 @@ describe('IDE commit module actions', () => {
it('redirects to new merge request page', (done) => {
jest.spyOn(eventHub, '$on').mockImplementation();
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
store.state.commit.shouldCreateMR = true;
store
@@ -468,7 +471,7 @@ describe('IDE commit module actions', () => {
it('does not redirect to new merge request page when shouldCreateMR is not checked', (done) => {
jest.spyOn(eventHub, '$on').mockImplementation();
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
store.state.commit.shouldCreateMR = false;
store
@@ -483,7 +486,7 @@ describe('IDE commit module actions', () => {
it('does not redirect to merge request page if shouldCreateMR is checked, but branch is the default branch', async () => {
jest.spyOn(eventHub, '$on').mockImplementation();
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.commit.commitAction = COMMIT_TO_CURRENT_BRANCH;
store.state.commit.shouldCreateMR = true;
await store.dispatch('commit/commitChanges');
diff --git a/spec/frontend/ide/stores/modules/commit/getters_spec.js b/spec/frontend/ide/stores/modules/commit/getters_spec.js
index 66ed51dbd13..0dc938bb637 100644
--- a/spec/frontend/ide/stores/modules/commit/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/getters_spec.js
@@ -1,6 +1,9 @@
-import commitState from '~/ide/stores/modules/commit/state';
+import {
+ COMMIT_TO_CURRENT_BRANCH,
+ COMMIT_TO_NEW_BRANCH,
+} from '~/ide/stores/modules/commit/constants';
import * as getters from '~/ide/stores/modules/commit/getters';
-import consts from '~/ide/stores/modules/commit/constants';
+import commitState from '~/ide/stores/modules/commit/state';
describe('IDE commit module getters', () => {
let state;
@@ -147,13 +150,13 @@ describe('IDE commit module getters', () => {
describe('isCreatingNewBranch', () => {
it('returns false if NOT creating a new branch', () => {
- state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ state.commitAction = COMMIT_TO_CURRENT_BRANCH;
expect(getters.isCreatingNewBranch(state)).toBeFalsy();
});
it('returns true if creating a new branch', () => {
- state.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ state.commitAction = COMMIT_TO_NEW_BRANCH;
expect(getters.isCreatingNewBranch(state)).toBeTruthy();
});
diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
index 6393a70eac6..50342832d75 100644
--- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
@@ -1,6 +1,6 @@
-import commitState from '~/ide/stores/modules/commit/state';
-import mutations from '~/ide/stores/modules/commit/mutations';
import * as types from '~/ide/stores/modules/commit/mutation_types';
+import mutations from '~/ide/stores/modules/commit/mutations';
+import commitState from '~/ide/stores/modules/commit/state';
describe('IDE commit module mutations', () => {
let state;
diff --git a/spec/frontend/ide/stores/modules/editor/actions_spec.js b/spec/frontend/ide/stores/modules/editor/actions_spec.js
index 6a420ac32de..f006018364b 100644
--- a/spec/frontend/ide/stores/modules/editor/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/editor/actions_spec.js
@@ -1,6 +1,6 @@
import testAction from 'helpers/vuex_action_helper';
-import * as types from '~/ide/stores/modules/editor/mutation_types';
import * as actions from '~/ide/stores/modules/editor/actions';
+import * as types from '~/ide/stores/modules/editor/mutation_types';
import { createTriggerRenamePayload } from '../../../helpers';
describe('~/ide/stores/modules/editor/actions', () => {
diff --git a/spec/frontend/ide/stores/modules/editor/getters_spec.js b/spec/frontend/ide/stores/modules/editor/getters_spec.js
index 55e1e31f66f..14099cdaeb2 100644
--- a/spec/frontend/ide/stores/modules/editor/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/editor/getters_spec.js
@@ -1,5 +1,5 @@
-import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils';
import * as getters from '~/ide/stores/modules/editor/getters';
+import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils';
const TEST_PATH = 'test/path.md';
const TEST_FILE_EDITOR = {
diff --git a/spec/frontend/ide/stores/modules/editor/mutations_spec.js b/spec/frontend/ide/stores/modules/editor/mutations_spec.js
index e4b330b3174..35d13f375a3 100644
--- a/spec/frontend/ide/stores/modules/editor/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/editor/mutations_spec.js
@@ -1,6 +1,6 @@
-import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils';
import * as types from '~/ide/stores/modules/editor/mutation_types';
import mutations from '~/ide/stores/modules/editor/mutations';
+import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils';
import { createTriggerRenamePayload } from '../../../helpers';
const TEST_PATH = 'test/path.md';
diff --git a/spec/frontend/ide/stores/modules/file_templates/actions_spec.js b/spec/frontend/ide/stores/modules/file_templates/actions_spec.js
index 76898e83c7a..9ff950b0875 100644
--- a/spec/frontend/ide/stores/modules/file_templates/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/file_templates/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import createState from '~/ide/stores/modules/file_templates/state';
import * as actions from '~/ide/stores/modules/file_templates/actions';
import * as types from '~/ide/stores/modules/file_templates/mutation_types';
+import createState from '~/ide/stores/modules/file_templates/state';
+import axios from '~/lib/utils/axios_utils';
describe('IDE file templates actions', () => {
let state;
diff --git a/spec/frontend/ide/stores/modules/file_templates/getters_spec.js b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
index c9676b23fa1..e237b167f96 100644
--- a/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
@@ -1,6 +1,6 @@
-import createState from '~/ide/stores/state';
import { leftSidebarViews } from '~/ide/constants';
import * as getters from '~/ide/stores/modules/file_templates/getters';
+import createState from '~/ide/stores/state';
describe('IDE file templates getters', () => {
describe('templateTypes', () => {
diff --git a/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
index 6a1a826093c..3ea3c9507dd 100644
--- a/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
@@ -1,6 +1,6 @@
-import createState from '~/ide/stores/modules/file_templates/state';
import * as types from '~/ide/stores/modules/file_templates/mutation_types';
import mutations from '~/ide/stores/modules/file_templates/mutations';
+import createState from '~/ide/stores/modules/file_templates/state';
const mockFileTemplates = [['MIT'], ['CC']];
const mockTemplateType = 'test';
diff --git a/spec/frontend/ide/stores/modules/merge_requests/actions_spec.js b/spec/frontend/ide/stores/modules/merge_requests/actions_spec.js
index 6594d65f558..e1f2b165dd9 100644
--- a/spec/frontend/ide/stores/modules/merge_requests/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/merge_requests/actions_spec.js
@@ -1,8 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import state from '~/ide/stores/modules/merge_requests/state';
-import * as types from '~/ide/stores/modules/merge_requests/mutation_types';
import {
requestMergeRequests,
receiveMergeRequestsError,
@@ -10,6 +7,9 @@ import {
fetchMergeRequests,
resetMergeRequests,
} from '~/ide/stores/modules/merge_requests/actions';
+import * as types from '~/ide/stores/modules/merge_requests/mutation_types';
+import state from '~/ide/stores/modules/merge_requests/state';
+import axios from '~/lib/utils/axios_utils';
import { mergeRequests } from '../../../mock_data';
describe('IDE merge requests actions', () => {
diff --git a/spec/frontend/ide/stores/modules/merge_requests/mutations_spec.js b/spec/frontend/ide/stores/modules/merge_requests/mutations_spec.js
index d33bda3652d..f45c577f801 100644
--- a/spec/frontend/ide/stores/modules/merge_requests/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/merge_requests/mutations_spec.js
@@ -1,7 +1,7 @@
import { TEST_HOST } from 'helpers/test_constants';
-import state from '~/ide/stores/modules/merge_requests/state';
-import mutations from '~/ide/stores/modules/merge_requests/mutations';
import * as types from '~/ide/stores/modules/merge_requests/mutation_types';
+import mutations from '~/ide/stores/modules/merge_requests/mutations';
+import state from '~/ide/stores/modules/merge_requests/state';
import { mergeRequests } from '../../../mock_data';
describe('IDE merge requests mutations', () => {
diff --git a/spec/frontend/ide/stores/modules/pane/mutations_spec.js b/spec/frontend/ide/stores/modules/pane/mutations_spec.js
index b5fcd35912e..eaeb2c8cd28 100644
--- a/spec/frontend/ide/stores/modules/pane/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/pane/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/ide/stores/modules/pane/state';
-import mutations from '~/ide/stores/modules/pane/mutations';
import * as types from '~/ide/stores/modules/pane/mutation_types';
+import mutations from '~/ide/stores/modules/pane/mutations';
+import state from '~/ide/stores/modules/pane/state';
describe('IDE pane module mutations', () => {
const TEST_VIEW = 'test-view';
diff --git a/spec/frontend/ide/stores/modules/pipelines/actions_spec.js b/spec/frontend/ide/stores/modules/pipelines/actions_spec.js
index b7ed257e954..9aa31136c89 100644
--- a/spec/frontend/ide/stores/modules/pipelines/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/pipelines/actions_spec.js
@@ -1,8 +1,8 @@
-import Visibility from 'visibilityjs';
import MockAdapter from 'axios-mock-adapter';
+import Visibility from 'visibilityjs';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
+import { rightSidebarViews } from '~/ide/constants';
import {
requestLatestPipeline,
receiveLatestPipelineError,
@@ -22,9 +22,9 @@ import {
fetchJobLogs,
resetLatestPipeline,
} from '~/ide/stores/modules/pipelines/actions';
-import state from '~/ide/stores/modules/pipelines/state';
import * as types from '~/ide/stores/modules/pipelines/mutation_types';
-import { rightSidebarViews } from '~/ide/constants';
+import state from '~/ide/stores/modules/pipelines/state';
+import axios from '~/lib/utils/axios_utils';
import { pipelines, jobs } from '../../../mock_data';
describe('IDE pipelines actions', () => {
diff --git a/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js b/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js
index d820bf0291e..0e738b98918 100644
--- a/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js
@@ -1,6 +1,6 @@
+import * as types from '~/ide/stores/modules/pipelines/mutation_types';
import mutations from '~/ide/stores/modules/pipelines/mutations';
import state from '~/ide/stores/modules/pipelines/state';
-import * as types from '~/ide/stores/modules/pipelines/mutation_types';
import { fullPipelinesResponse, stages, jobs } from '../../../mock_data';
describe('IDE pipelines mutations', () => {
diff --git a/spec/frontend/ide/stores/modules/router/mutations_spec.js b/spec/frontend/ide/stores/modules/router/mutations_spec.js
index a4a83c9344d..5a9f266db94 100644
--- a/spec/frontend/ide/stores/modules/router/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/router/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/ide/stores/modules/router/mutations';
import * as types from '~/ide/stores/modules/router/mutation_types';
+import mutations from '~/ide/stores/modules/router/mutations';
import createState from '~/ide/stores/modules/router/state';
const TEST_PATH = 'test/path/abc';
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
index 05935f1db38..e5887ca0a33 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
@@ -1,14 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
+import * as actions from '~/ide/stores/modules/terminal/actions/checks';
import {
CHECK_CONFIG,
CHECK_RUNNERS,
RETRY_RUNNERS_INTERVAL,
} from '~/ide/stores/modules/terminal/constants';
-import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
import * as messages from '~/ide/stores/modules/terminal/messages';
-import * as actions from '~/ide/stores/modules/terminal/actions/checks';
+import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
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 dd51786745f..e42e760b841 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,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+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';
import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
-import * as actions from '~/ide/stores/modules/terminal/actions/session_controls';
-import httpStatus from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
+import httpStatus from '~/lib/utils/http_status';
jest.mock('~/flash');
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 0e123dce798..0227955754c 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,11 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+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';
import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
-import * as actions from '~/ide/stores/modules/terminal/actions/session_status';
import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/setup_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/setup_spec.js
index 8bf3b58228e..a823c05c459 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/setup_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/setup_spec.js
@@ -1,6 +1,6 @@
import testAction from 'helpers/vuex_action_helper';
-import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
import * as actions from '~/ide/stores/modules/terminal/actions/setup';
+import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
describe('IDE store terminal setup actions', () => {
describe('init', () => {
diff --git a/spec/frontend/ide/stores/modules/terminal/messages_spec.js b/spec/frontend/ide/stores/modules/terminal/messages_spec.js
index 1bb92a9dfa5..e8f375a70b5 100644
--- a/spec/frontend/ide/stores/modules/terminal/messages_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/messages_spec.js
@@ -1,8 +1,8 @@
import { escape } from 'lodash';
import { TEST_HOST } from 'spec/test_constants';
import * as messages from '~/ide/stores/modules/terminal/messages';
-import { sprintf } from '~/locale';
import httpStatus from '~/lib/utils/http_status';
+import { sprintf } from '~/locale';
const TEST_HELP_URL = `${TEST_HOST}/help`;
diff --git a/spec/frontend/ide/stores/modules/terminal/mutations_spec.js b/spec/frontend/ide/stores/modules/terminal/mutations_spec.js
index e9933bdd7be..3451932a185 100644
--- a/spec/frontend/ide/stores/modules/terminal/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/mutations_spec.js
@@ -4,9 +4,9 @@ import {
RUNNING,
STOPPING,
} from '~/ide/stores/modules/terminal/constants';
-import createState from '~/ide/stores/modules/terminal/state';
import * as types from '~/ide/stores/modules/terminal/mutation_types';
import mutations from '~/ide/stores/modules/terminal/mutations';
+import createState from '~/ide/stores/modules/terminal/state';
describe('IDE store terminal mutations', () => {
let state;
diff --git a/spec/frontend/ide/stores/modules/terminal_sync/actions_spec.js b/spec/frontend/ide/stores/modules/terminal_sync/actions_spec.js
index 2ae7e8a8727..22b0615c6d0 100644
--- a/spec/frontend/ide/stores/modules/terminal_sync/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal_sync/actions_spec.js
@@ -1,6 +1,6 @@
import testAction from 'helpers/vuex_action_helper';
-import * as actions from '~/ide/stores/modules/terminal_sync/actions';
import mirror, { canConnect, SERVICE_NAME } from '~/ide/lib/mirror';
+import * as actions from '~/ide/stores/modules/terminal_sync/actions';
import * as types from '~/ide/stores/modules/terminal_sync/mutation_types';
jest.mock('~/ide/lib/mirror');
diff --git a/spec/frontend/ide/stores/modules/terminal_sync/mutations_spec.js b/spec/frontend/ide/stores/modules/terminal_sync/mutations_spec.js
index ecf35d60e96..b7dbf93f4e6 100644
--- a/spec/frontend/ide/stores/modules/terminal_sync/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal_sync/mutations_spec.js
@@ -1,6 +1,6 @@
-import createState from '~/ide/stores/modules/terminal_sync/state';
import * as types from '~/ide/stores/modules/terminal_sync/mutation_types';
import mutations from '~/ide/stores/modules/terminal_sync/mutations';
+import createState from '~/ide/stores/modules/terminal_sync/state';
const TEST_MESSAGE = 'lorem ipsum dolar sit';
diff --git a/spec/frontend/ide/stores/mutations/file_spec.js b/spec/frontend/ide/stores/mutations/file_spec.js
index 9bbdac0ae25..825d2a546cd 100644
--- a/spec/frontend/ide/stores/mutations/file_spec.js
+++ b/spec/frontend/ide/stores/mutations/file_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/ide/stores/mutations/file';
import { createStore } from '~/ide/stores';
+import mutations from '~/ide/stores/mutations/file';
import { file } from '../../helpers';
describe('IDE store file mutations', () => {
diff --git a/spec/frontend/ide/stores/plugins/terminal_spec.js b/spec/frontend/ide/stores/plugins/terminal_spec.js
index 948c2131fd8..d4cdad16ecb 100644
--- a/spec/frontend/ide/stores/plugins/terminal_spec.js
+++ b/spec/frontend/ide/stores/plugins/terminal_spec.js
@@ -2,8 +2,8 @@ import { createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import terminalModule from '~/ide/stores/modules/terminal';
-import createTerminalPlugin from '~/ide/stores/plugins/terminal';
import { SET_BRANCH_WORKING_REFERENCE } from '~/ide/stores/mutation_types';
+import createTerminalPlugin from '~/ide/stores/plugins/terminal';
const TEST_DATASET = {
eeWebTerminalSvgPath: `${TEST_HOST}/web/terminal/svg`,
diff --git a/spec/frontend/ide/stores/plugins/terminal_sync_spec.js b/spec/frontend/ide/stores/plugins/terminal_sync_spec.js
index 0e5f4184679..f12f80c1602 100644
--- a/spec/frontend/ide/stores/plugins/terminal_sync_spec.js
+++ b/spec/frontend/ide/stores/plugins/terminal_sync_spec.js
@@ -1,9 +1,9 @@
+import eventHub from '~/ide/eventhub';
+import { createStore } from '~/ide/stores';
+import { RUNNING, STOPPING } from '~/ide/stores/modules/terminal/constants';
+import { SET_SESSION_STATUS } from '~/ide/stores/modules/terminal/mutation_types';
import createTerminalPlugin from '~/ide/stores/plugins/terminal';
import createTerminalSyncPlugin from '~/ide/stores/plugins/terminal_sync';
-import { SET_SESSION_STATUS } from '~/ide/stores/modules/terminal/mutation_types';
-import { RUNNING, STOPPING } from '~/ide/stores/modules/terminal/constants';
-import { createStore } from '~/ide/stores';
-import eventHub from '~/ide/eventhub';
import { createTriggerUpdatePayload } from '../../helpers';
jest.mock('~/ide/lib/mirror');
diff --git a/spec/frontend/ide/stores/utils_spec.js b/spec/frontend/ide/stores/utils_spec.js
index b185013050e..46a0794b2e6 100644
--- a/spec/frontend/ide/stores/utils_spec.js
+++ b/spec/frontend/ide/stores/utils_spec.js
@@ -1,5 +1,5 @@
-import * as utils from '~/ide/stores/utils';
import { commitActionTypes } from '~/ide/constants';
+import * as utils from '~/ide/stores/utils';
import { file } from '../helpers';
describe('Multi-file store utils', () => {
diff --git a/spec/frontend/image_diff/image_badge_spec.js b/spec/frontend/image_diff/image_badge_spec.js
index a11b50ead47..8450b9b4694 100644
--- a/spec/frontend/image_diff/image_badge_spec.js
+++ b/spec/frontend/image_diff/image_badge_spec.js
@@ -1,5 +1,5 @@
-import ImageBadge from '~/image_diff/image_badge';
import imageDiffHelper from '~/image_diff/helpers/index';
+import ImageBadge from '~/image_diff/image_badge';
import * as mockData from './mock_data';
describe('ImageBadge', () => {
diff --git a/spec/frontend/image_diff/image_diff_spec.js b/spec/frontend/image_diff/image_diff_spec.js
index e14f8dc774f..16d19f45496 100644
--- a/spec/frontend/image_diff/image_diff_spec.js
+++ b/spec/frontend/image_diff/image_diff_spec.js
@@ -1,7 +1,7 @@
import { TEST_HOST } from 'helpers/test_constants';
+import imageDiffHelper from '~/image_diff/helpers/index';
import ImageDiff from '~/image_diff/image_diff';
import * as imageUtility from '~/lib/utils/image_utility';
-import imageDiffHelper from '~/image_diff/helpers/index';
import * as mockData from './mock_data';
describe('ImageDiff', () => {
diff --git a/spec/frontend/image_diff/init_discussion_tab_spec.js b/spec/frontend/image_diff/init_discussion_tab_spec.js
index f96d00230ee..5bc0c738944 100644
--- a/spec/frontend/image_diff/init_discussion_tab_spec.js
+++ b/spec/frontend/image_diff/init_discussion_tab_spec.js
@@ -1,5 +1,5 @@
-import initDiscussionTab from '~/image_diff/init_discussion_tab';
import initImageDiffHelper from '~/image_diff/helpers/init_image_diff';
+import initDiscussionTab from '~/image_diff/init_discussion_tab';
describe('initDiscussionTab', () => {
beforeEach(() => {
diff --git a/spec/frontend/image_diff/replaced_image_diff_spec.js b/spec/frontend/image_diff/replaced_image_diff_spec.js
index 16be4b82ad7..cc4a2530fc4 100644
--- a/spec/frontend/image_diff/replaced_image_diff_spec.js
+++ b/spec/frontend/image_diff/replaced_image_diff_spec.js
@@ -1,8 +1,8 @@
import { TEST_HOST } from 'helpers/test_constants';
-import ReplacedImageDiff from '~/image_diff/replaced_image_diff';
+import imageDiffHelper from '~/image_diff/helpers/index';
import ImageDiff from '~/image_diff/image_diff';
+import ReplacedImageDiff from '~/image_diff/replaced_image_diff';
import { viewTypes } from '~/image_diff/view_types';
-import imageDiffHelper from '~/image_diff/helpers/index';
describe('ReplacedImageDiff', () => {
let element;
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
index ac8b73aeb49..cdef4b1ee62 100644
--- a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlLink, GlFormInput } from '@gitlab/ui';
-import Select2Select from '~/vue_shared/components/select2_select.vue';
-import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
+import { shallowMount } from '@vue/test-utils';
import { STATUSES } from '~/import_entities/constants';
+import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
+import Select2Select from '~/vue_shared/components/select2_select.vue';
import { availableNamespacesFixture } from '../graphql/fixtures';
const getFakeGroup = (status) => ({
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 cd184bb65cc..dd734782169 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
@@ -1,15 +1,15 @@
+import { GlEmptyState, GlLoadingIcon, GlSearchBoxByClick, GlSprintf } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
-import { GlLoadingIcon } from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
-import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import { STATUSES } from '~/import_entities/constants';
import ImportTable from '~/import_entities/import_groups/components/import_table.vue';
-import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
-import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
+import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
-
-import { STATUSES } from '~/import_entities/constants';
+import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
+import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
+import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import { availableNamespacesFixture, generateFakeEntry } from '../graphql/fixtures';
@@ -20,6 +20,9 @@ describe('import table', () => {
let wrapper;
let apolloProvider;
+ const FAKE_GROUP = generateFakeEntry({ id: 1, status: STATUSES.NONE });
+ const FAKE_PAGE_INFO = { page: 1, perPage: 20, total: 40, totalPages: 2 };
+
const createComponent = ({ bulkImportSourceGroups }) => {
apolloProvider = createMockApollo([], {
Query: {
@@ -34,6 +37,12 @@ describe('import table', () => {
});
wrapper = shallowMount(ImportTable, {
+ propsData: {
+ sourceUrl: 'https://demo.host',
+ },
+ stubs: {
+ GlSprintf,
+ },
localVue,
apolloProvider,
});
@@ -62,25 +71,50 @@ describe('import table', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
+ it('renders message about empty state when no groups are available for import', async () => {
+ createComponent({
+ bulkImportSourceGroups: () => ({
+ nodes: [],
+ pageInfo: FAKE_PAGE_INFO,
+ }),
+ });
+ await waitForPromises();
+
+ expect(wrapper.find(GlEmptyState).props().title).toBe('No groups available for import');
+ });
+
it('renders import row for each group in response', async () => {
const FAKE_GROUPS = [
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
generateFakeEntry({ id: 2, status: STATUSES.FINISHED }),
];
createComponent({
- bulkImportSourceGroups: () => FAKE_GROUPS,
+ bulkImportSourceGroups: () => ({
+ nodes: FAKE_GROUPS,
+ pageInfo: FAKE_PAGE_INFO,
+ }),
});
await waitForPromises();
expect(wrapper.findAll(ImportTableRow)).toHaveLength(FAKE_GROUPS.length);
});
- describe('converts row events to mutation invocations', () => {
- const FAKE_GROUP = generateFakeEntry({ id: 1, status: STATUSES.NONE });
+ it('does not render status string when result list is empty', async () => {
+ createComponent({
+ bulkImportSourceGroups: jest.fn().mockResolvedValue({
+ nodes: [],
+ pageInfo: FAKE_PAGE_INFO,
+ }),
+ });
+ await waitForPromises();
+
+ expect(wrapper.text()).not.toContain('Showing 1-0');
+ });
+ describe('converts row events to mutation invocations', () => {
beforeEach(() => {
createComponent({
- bulkImportSourceGroups: () => [FAKE_GROUP],
+ bulkImportSourceGroups: () => ({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }),
});
return waitForPromises();
});
@@ -100,4 +134,115 @@ describe('import table', () => {
});
});
});
+
+ describe('pagination', () => {
+ const bulkImportSourceGroupsQueryMock = jest
+ .fn()
+ .mockResolvedValue({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO });
+
+ beforeEach(() => {
+ createComponent({
+ bulkImportSourceGroups: bulkImportSourceGroupsQueryMock,
+ });
+ return waitForPromises();
+ });
+
+ it('correctly passes pagination info from query', () => {
+ expect(wrapper.find(PaginationLinks).props().pageInfo).toStrictEqual(FAKE_PAGE_INFO);
+ });
+
+ it('updates page when page change is requested', async () => {
+ const REQUESTED_PAGE = 2;
+ wrapper.find(PaginationLinks).props().change(REQUESTED_PAGE);
+
+ await waitForPromises();
+ expect(bulkImportSourceGroupsQueryMock).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ page: REQUESTED_PAGE }),
+ expect.anything(),
+ expect.anything(),
+ );
+ });
+
+ it('updates status text when page is changed', async () => {
+ const REQUESTED_PAGE = 2;
+ bulkImportSourceGroupsQueryMock.mockResolvedValue({
+ nodes: [FAKE_GROUP],
+ pageInfo: {
+ page: 2,
+ total: 38,
+ perPage: 20,
+ totalPages: 2,
+ },
+ });
+ wrapper.find(PaginationLinks).props().change(REQUESTED_PAGE);
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain('Showing 21-21 of 38');
+ });
+ });
+
+ describe('filters', () => {
+ const bulkImportSourceGroupsQueryMock = jest
+ .fn()
+ .mockResolvedValue({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO });
+
+ beforeEach(() => {
+ createComponent({
+ bulkImportSourceGroups: bulkImportSourceGroupsQueryMock,
+ });
+ return waitForPromises();
+ });
+
+ const findFilterInput = () => wrapper.find(GlSearchBoxByClick);
+
+ it('properly passes filter to graphql query when search box is submitted', async () => {
+ createComponent({
+ bulkImportSourceGroups: bulkImportSourceGroupsQueryMock,
+ });
+ await waitForPromises();
+
+ const FILTER_VALUE = 'foo';
+ findFilterInput().vm.$emit('submit', FILTER_VALUE);
+ await waitForPromises();
+
+ expect(bulkImportSourceGroupsQueryMock).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ filter: FILTER_VALUE }),
+ expect.anything(),
+ expect.anything(),
+ );
+ });
+
+ it('updates status string when search box is submitted', async () => {
+ createComponent({
+ bulkImportSourceGroups: bulkImportSourceGroupsQueryMock,
+ });
+ await waitForPromises();
+
+ const FILTER_VALUE = 'foo';
+ findFilterInput().vm.$emit('submit', FILTER_VALUE);
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain('Showing 1-1 of 40 groups matching filter "foo"');
+ });
+
+ it('properly resets filter in graphql query when search box is cleared', async () => {
+ const FILTER_VALUE = 'foo';
+ findFilterInput().vm.$emit('submit', FILTER_VALUE);
+ await waitForPromises();
+
+ bulkImportSourceGroupsQueryMock.mockClear();
+ await apolloProvider.defaultClient.resetStore();
+ findFilterInput().vm.$emit('clear');
+ await waitForPromises();
+
+ expect(bulkImportSourceGroupsQueryMock).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ filter: '' }),
+ expect.anything(),
+ expect.anything(),
+ );
+ });
+ });
});
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 514ed411138..4d3d2c41bbe 100644
--- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
+++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
@@ -1,20 +1,20 @@
-import MockAdapter from 'axios-mock-adapter';
import { InMemoryCache } from 'apollo-cache-inmemory';
+import MockAdapter from 'axios-mock-adapter';
import { createMockClient } from 'mock-apollo-client';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
+import { STATUSES } from '~/import_entities/constants';
import {
clientTypenames,
createResolvers,
} from '~/import_entities/import_groups/graphql/client_factory';
+import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
+import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
+import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
+import availableNamespacesQuery from '~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql';
+import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
-import { STATUSES } from '~/import_entities/constants';
-import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
-import availableNamespacesQuery from '~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql';
-import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
-import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
-import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
+import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import { statusEndpointFixture, availableNamespacesFixture } from './fixtures';
@@ -28,6 +28,7 @@ const FAKE_ENDPOINTS = {
status: '/fake_status_url',
availableNamespaces: '/fake_available_namespaces',
createBulkImport: '/fake_create_bulk_import',
+ jobs: '/fake_jobs',
};
describe('Bulk import resolvers', () => {
@@ -79,33 +80,61 @@ describe('Bulk import resolvers', () => {
axiosMockAdapter
.onGet(FAKE_ENDPOINTS.availableNamespaces)
.reply(httpStatus.OK, availableNamespacesFixture);
-
- const response = await client.query({ query: bulkImportSourceGroupsQuery });
- results = response.data.bulkImportSourceGroups;
});
- it('mirrors REST endpoint response fields', () => {
- const MIRRORED_FIELDS = ['id', 'full_name', 'full_path', 'web_url'];
- expect(
- results.every((r, idx) =>
- MIRRORED_FIELDS.every(
- (field) => r[field] === statusEndpointFixture.importable_data[idx][field],
+ describe('when called', () => {
+ beforeEach(async () => {
+ const response = await client.query({ query: bulkImportSourceGroupsQuery });
+ results = response.data.bulkImportSourceGroups.nodes;
+ });
+
+ it('mirrors REST endpoint response fields', () => {
+ const MIRRORED_FIELDS = ['id', 'full_name', 'full_path', 'web_url'];
+ expect(
+ results.every((r, idx) =>
+ MIRRORED_FIELDS.every(
+ (field) => r[field] === statusEndpointFixture.importable_data[idx][field],
+ ),
),
- ),
- ).toBe(true);
- });
+ ).toBe(true);
+ });
- it('populates each result instance with status field default to none', () => {
- expect(results.every((r) => r.status === STATUSES.NONE)).toBe(true);
- });
+ it('populates each result instance with status field default to none', () => {
+ expect(results.every((r) => r.status === STATUSES.NONE)).toBe(true);
+ });
+
+ it('populates each result instance with import_target defaulted to first available namespace', () => {
+ expect(
+ results.every(
+ (r) => r.import_target.target_namespace === availableNamespacesFixture[0].full_path,
+ ),
+ ).toBe(true);
+ });
- it('populates each result instance with import_target defaulted to first available namespace', () => {
- expect(
- results.every(
- (r) => r.import_target.target_namespace === availableNamespacesFixture[0].full_path,
- ),
- ).toBe(true);
+ it('starts polling when request completes', async () => {
+ const [statusPoller] = StatusPoller.mock.instances;
+ expect(statusPoller.startPolling).toHaveBeenCalled();
+ });
});
+
+ it.each`
+ variable | queryParam | value
+ ${'filter'} | ${'filter'} | ${'demo'}
+ ${'perPage'} | ${'per_page'} | ${30}
+ ${'page'} | ${'page'} | ${3}
+ `(
+ 'properly passes GraphQL variable $variable as REST $queryParam query parameter',
+ async ({ variable, queryParam, value }) => {
+ await client.query({
+ query: bulkImportSourceGroupsQuery,
+ variables: { [variable]: value },
+ });
+ const restCall = axiosMockAdapter.history.get.find(
+ (q) => q.url === FAKE_ENDPOINTS.status,
+ );
+ expect(restCall.params[queryParam]).toBe(value);
+ },
+ );
});
});
@@ -117,20 +146,28 @@ describe('Bulk import resolvers', () => {
client.writeQuery({
query: bulkImportSourceGroupsQuery,
data: {
- bulkImportSourceGroups: [
- {
- __typename: clientTypenames.BulkImportSourceGroup,
- id: GROUP_ID,
- status: STATUSES.NONE,
- web_url: 'https://fake.host/1',
- full_path: 'fake_group_1',
- full_name: 'fake_name_1',
- import_target: {
- target_namespace: 'root',
- new_name: 'group1',
+ bulkImportSourceGroups: {
+ nodes: [
+ {
+ __typename: clientTypenames.BulkImportSourceGroup,
+ id: GROUP_ID,
+ status: STATUSES.NONE,
+ web_url: 'https://fake.host/1',
+ full_path: 'fake_group_1',
+ full_name: 'fake_name_1',
+ import_target: {
+ target_namespace: 'root',
+ new_name: 'group1',
+ },
},
+ ],
+ pageInfo: {
+ page: 1,
+ perPage: 20,
+ total: 37,
+ totalPages: 2,
},
- ],
+ },
},
});
@@ -140,7 +177,7 @@ describe('Bulk import resolvers', () => {
fetchPolicy: 'cache-only',
})
.subscribe(({ data }) => {
- results = data.bulkImportSourceGroups;
+ results = data.bulkImportSourceGroups.nodes;
});
});
@@ -174,7 +211,9 @@ describe('Bulk import resolvers', () => {
});
await waitForPromises();
- const { bulkImportSourceGroups: intermediateResults } = client.readQuery({
+ const {
+ bulkImportSourceGroups: { nodes: intermediateResults },
+ } = client.readQuery({
query: bulkImportSourceGroupsQuery,
});
@@ -182,7 +221,7 @@ describe('Bulk import resolvers', () => {
});
it('sets group status to STARTED when request completes', async () => {
- axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK);
+ axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK, { id: 1 });
await client.mutate({
mutation: importGroupMutation,
variables: { sourceGroupId: GROUP_ID },
@@ -191,16 +230,6 @@ describe('Bulk import resolvers', () => {
expect(results[0].status).toBe(STATUSES.STARTED);
});
- it('starts polling when request completes', async () => {
- axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK);
- await client.mutate({
- mutation: importGroupMutation,
- variables: { sourceGroupId: GROUP_ID },
- });
- const [statusPoller] = StatusPoller.mock.instances;
- expect(statusPoller.startPolling).toHaveBeenCalled();
- });
-
it('resets status to NONE if request fails', async () => {
axiosMockAdapter
.onPost(FAKE_ENDPOINTS.createBulkImport)
diff --git a/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
index 5940ea544ea..ca987ab3ab4 100644
--- a/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
+++ b/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
@@ -1,7 +1,7 @@
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
-import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
-import ImportSourceGroupFragment from '~/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql';
import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory';
+import ImportSourceGroupFragment from '~/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql';
+import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
describe('SourceGroupsManager', () => {
let manager;
diff --git a/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
index e7f1626f81d..a5fc4e18a02 100644
--- a/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
+++ b/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
@@ -1,215 +1,113 @@
-import { createMockClient } from 'mock-apollo-client';
-import { InMemoryCache } from 'apollo-cache-inmemory';
-import waitForPromises from 'helpers/wait_for_promises';
-
+import MockAdapter from 'axios-mock-adapter';
+import Visibility from 'visibilityjs';
import createFlash from '~/flash';
-import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
-import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
import { STATUSES } from '~/import_entities/constants';
import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
-import { generateFakeEntry } from '../fixtures';
+import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
+import axios from '~/lib/utils/axios_utils';
+import Poll from '~/lib/utils/poll';
+jest.mock('visibilityjs');
jest.mock('~/flash');
+jest.mock('~/lib/utils/poll');
jest.mock('~/import_entities/import_groups/graphql/services/source_groups_manager', () => ({
SourceGroupsManager: jest.fn().mockImplementation(function mock() {
this.setImportStatus = jest.fn();
+ this.findByImportId = jest.fn();
}),
}));
-const TEST_POLL_INTERVAL = 1000;
+const FAKE_POLL_PATH = '/fake/poll/path';
+const CLIENT_MOCK = {};
describe('Bulk import status poller', () => {
let poller;
- let clientMock;
+ let mockAdapter;
- const listQueryCacheCalls = () =>
- clientMock.readQuery.mock.calls.filter((call) => call[0].query === bulkImportSourceGroupsQuery);
+ const getPollHistory = () => mockAdapter.history.get.filter((x) => x.url === FAKE_POLL_PATH);
beforeEach(() => {
- clientMock = createMockClient({
- cache: new InMemoryCache({
- fragmentMatcher: { match: () => true },
- }),
- });
-
- jest.spyOn(clientMock, 'readQuery');
-
- poller = new StatusPoller({
- client: clientMock,
- interval: TEST_POLL_INTERVAL,
- });
+ mockAdapter = new MockAdapter(axios);
+ mockAdapter.onGet(FAKE_POLL_PATH).reply(200, {});
+ poller = new StatusPoller({ client: CLIENT_MOCK, pollPath: FAKE_POLL_PATH });
});
- describe('general behavior', () => {
- beforeEach(() => {
- clientMock.cache.writeQuery({
- query: bulkImportSourceGroupsQuery,
- data: { bulkImportSourceGroups: [] },
- });
- });
-
- it('does not perform polling when constructed', () => {
- jest.runOnlyPendingTimers();
- expect(listQueryCacheCalls()).toHaveLength(0);
- });
-
- it('immediately start polling when requested', async () => {
- await poller.startPolling();
- expect(listQueryCacheCalls()).toHaveLength(1);
- });
-
- it('constantly polls when started', async () => {
- poller.startPolling();
- expect(listQueryCacheCalls()).toHaveLength(1);
-
- jest.advanceTimersByTime(TEST_POLL_INTERVAL);
- expect(listQueryCacheCalls()).toHaveLength(2);
-
- jest.advanceTimersByTime(TEST_POLL_INTERVAL);
- expect(listQueryCacheCalls()).toHaveLength(3);
- });
-
- it('does not start polling when requested multiple times', async () => {
- poller.startPolling();
- expect(listQueryCacheCalls()).toHaveLength(1);
-
- poller.startPolling();
- expect(listQueryCacheCalls()).toHaveLength(1);
- });
-
- it('stops polling when requested', async () => {
- poller.startPolling();
- expect(listQueryCacheCalls()).toHaveLength(1);
-
- poller.stopPolling();
- jest.runOnlyPendingTimers();
- expect(listQueryCacheCalls()).toHaveLength(1);
- });
-
- it('does not query server when list is empty', async () => {
- jest.spyOn(clientMock, 'query');
- poller.startPolling();
- expect(clientMock.query).not.toHaveBeenCalled();
- });
+ it('creates source group manager with proper client', () => {
+ expect(SourceGroupsManager.mock.calls).toHaveLength(1);
+ const [[{ client }]] = SourceGroupsManager.mock.calls;
+ expect(client).toBe(CLIENT_MOCK);
});
- it('does not query server when no groups have STARTED status', async () => {
- clientMock.cache.writeQuery({
- query: bulkImportSourceGroupsQuery,
- data: {
- bulkImportSourceGroups: [STATUSES.NONE, STATUSES.FINISHED].map((status, idx) =>
- generateFakeEntry({ status, id: idx }),
- ),
- },
- });
-
- jest.spyOn(clientMock, 'query');
+ it('creates poller with proper config', () => {
+ expect(Poll.mock.calls).toHaveLength(1);
+ const [[pollConfig]] = Poll.mock.calls;
+ expect(typeof pollConfig.method).toBe('string');
+
+ const pollOperation = pollConfig.resource[pollConfig.method];
+ expect(typeof pollOperation).toBe('function');
+ });
+
+ it('invokes axios when polling is performed', async () => {
+ const [[pollConfig]] = Poll.mock.calls;
+ const pollOperation = pollConfig.resource[pollConfig.method];
+ expect(getPollHistory()).toHaveLength(0);
+
+ pollOperation();
+ await axios.waitForAll();
+
+ expect(getPollHistory()).toHaveLength(1);
+ });
+
+ it('subscribes to visibility changes', () => {
+ expect(Visibility.change).toHaveBeenCalled();
+ });
+
+ it.each`
+ isHidden | action
+ ${true} | ${'stop'}
+ ${false} | ${'restart'}
+ `('$action polling when hidden is $isHidden', ({ action, isHidden }) => {
+ const [pollInstance] = Poll.mock.instances;
+ const [[changeHandler]] = Visibility.change.mock.calls;
+ Visibility.hidden.mockReturnValue(isHidden);
+ expect(pollInstance[action]).not.toHaveBeenCalled();
+
+ changeHandler();
+
+ expect(pollInstance[action]).toHaveBeenCalled();
+ });
+
+ it('does not perform polling when constructed', async () => {
+ await axios.waitForAll();
+
+ expect(getPollHistory()).toHaveLength(0);
+ });
+
+ it('immediately start polling when requested', async () => {
+ const [pollInstance] = Poll.mock.instances;
+
poller.startPolling();
- expect(clientMock.query).not.toHaveBeenCalled();
+
+ expect(pollInstance.makeRequest).toHaveBeenCalled();
+ });
+
+ it('when error occurs shows flash with error', () => {
+ const [[pollConfig]] = Poll.mock.calls;
+ pollConfig.errorCallback();
+ expect(createFlash).toHaveBeenCalled();
});
- describe('when there are groups which have STARTED status', () => {
- const TARGET_NAMESPACE = 'root';
-
- const STARTED_GROUP_1 = {
- status: STATUSES.STARTED,
- id: 'started1',
- import_target: {
- target_namespace: TARGET_NAMESPACE,
- new_name: 'group1',
- },
- };
-
- const STARTED_GROUP_2 = {
- status: STATUSES.STARTED,
- id: 'started2',
- import_target: {
- target_namespace: TARGET_NAMESPACE,
- new_name: 'group2',
- },
- };
-
- const NOT_STARTED_GROUP = {
- status: STATUSES.NONE,
- id: 'not_started',
- import_target: {
- target_namespace: TARGET_NAMESPACE,
- new_name: 'group3',
- },
- };
-
- it('query server only for groups with STATUSES.STARTED', async () => {
- clientMock.cache.writeQuery({
- query: bulkImportSourceGroupsQuery,
- data: {
- bulkImportSourceGroups: [
- STARTED_GROUP_1,
- NOT_STARTED_GROUP,
- STARTED_GROUP_2,
- ].map((group) => generateFakeEntry(group)),
- },
- });
-
- clientMock.query = jest.fn().mockResolvedValue({ data: {} });
- poller.startPolling();
-
- expect(clientMock.query).toHaveBeenCalledTimes(1);
- await waitForPromises();
- const [[doc]] = clientMock.query.mock.calls;
- const { selections } = doc.query.definitions[0].selectionSet;
- expect(selections.every((field) => field.name.value === 'group')).toBeTruthy();
- expect(selections).toHaveLength(2);
- expect(selections.map((sel) => sel.arguments[0].value.value)).toStrictEqual([
- `${TARGET_NAMESPACE}/${STARTED_GROUP_1.import_target.new_name}`,
- `${TARGET_NAMESPACE}/${STARTED_GROUP_2.import_target.new_name}`,
- ]);
- });
-
- it('updates statuses only for groups in response', async () => {
- clientMock.cache.writeQuery({
- query: bulkImportSourceGroupsQuery,
- data: {
- bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map((group) =>
- generateFakeEntry(group),
- ),
- },
- });
-
- clientMock.query = jest.fn().mockResolvedValue({ data: { group0: {} } });
- poller.startPolling();
- await waitForPromises();
- const [managerInstance] = SourceGroupsManager.mock.instances;
- expect(managerInstance.setImportStatus).toHaveBeenCalledTimes(1);
- expect(managerInstance.setImportStatus).toHaveBeenCalledWith(
- expect.objectContaining({ id: STARTED_GROUP_1.id }),
- STATUSES.FINISHED,
- );
- });
-
- describe('when error occurs', () => {
- beforeEach(() => {
- clientMock.cache.writeQuery({
- query: bulkImportSourceGroupsQuery,
- data: {
- bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map((group) =>
- generateFakeEntry(group),
- ),
- },
- });
-
- clientMock.query = jest.fn().mockRejectedValue(new Error('dummy error'));
- poller.startPolling();
- return waitForPromises();
- });
-
- it('reports an error', () => {
- expect(createFlash).toHaveBeenCalled();
- });
-
- it('continues polling', async () => {
- jest.advanceTimersByTime(TEST_POLL_INTERVAL);
- expect(listQueryCacheCalls()).toHaveLength(2);
- });
- });
+ it('when success response arrives updates relevant group status', () => {
+ const FAKE_ID = 5;
+ const [[pollConfig]] = Poll.mock.calls;
+ const [managerInstance] = SourceGroupsManager.mock.instances;
+ managerInstance.findByImportId.mockReturnValue({ id: FAKE_ID });
+
+ pollConfig.successCallback({ data: [{ id: FAKE_ID, status_name: STATUSES.FINISHED }] });
+
+ expect(managerInstance.setImportStatus).toHaveBeenCalledWith(
+ expect.objectContaining({ id: FAKE_ID }),
+ STATUSES.FINISHED,
+ );
});
});
diff --git a/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js b/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
index 8f8c01a8b81..ea88c361f7b 100644
--- a/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
@@ -1,7 +1,7 @@
-import { nextTick } from 'vue';
+import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
-import { GlAlert } from '@gitlab/ui';
import BitbucketStatusTable from '~/import_entities/import_projects/components/bitbucket_status_table.vue';
import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
diff --git a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
index 27f642d15c8..d9f4168f1a5 100644
--- a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
@@ -1,18 +1,20 @@
+import { GlLoadingIcon, GlButton, GlIntersectionObserver, GlFormInput } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlLoadingIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
-import state from '~/import_entities/import_projects/store/state';
-import * as getters from '~/import_entities/import_projects/store/getters';
import { STATUSES } from '~/import_entities/constants';
import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
+import * as getters from '~/import_entities/import_projects/store/getters';
+import state from '~/import_entities/import_projects/store/state';
describe('ImportProjectsTable', () => {
let wrapper;
const findFilterField = () =>
- wrapper.find('input[data-qa-selector="githubish_import_filter_field"]');
+ wrapper
+ .findAllComponents(GlFormInput)
+ .wrappers.find((w) => w.attributes('placeholder') === 'Filter your repositories by name');
const providerTitle = 'THE PROVIDER';
const providerRepo = {
@@ -205,7 +207,7 @@ describe('ImportProjectsTable', () => {
it('does not render filtering input field when filterable is false', () => {
createComponent({ filterable: false });
- expect(findFilterField().exists()).toBe(false);
+ expect(findFilterField()).toBeUndefined();
});
describe('when paginatable is set to true', () => {
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 2ed11ae277e..e15389be53a 100644
--- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -1,10 +1,10 @@
+import { GlBadge, GlButton } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlBadge } from '@gitlab/ui';
-import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
-import ImportStatus from '~/import_entities/components/import_status.vue';
import { STATUSES } from '~/import_entities//constants';
+import ImportStatus from '~/import_entities/components/import_status.vue';
+import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
import Select2Select from '~/vue_shared/components/select2_select.vue';
describe('ProviderRepoTableRow', () => {
@@ -34,7 +34,7 @@ describe('ProviderRepoTableRow', () => {
}
const findImportButton = () => {
- const buttons = wrapper.findAll('button').filter((node) => node.text() === 'Import');
+ const buttons = wrapper.findAllComponents(GlButton).filter((node) => node.text() === 'Import');
return buttons.length ? buttons.at(0) : buttons;
};
@@ -91,7 +91,7 @@ describe('ProviderRepoTableRow', () => {
});
it('imports repo when clicking import button', async () => {
- findImportButton().trigger('click');
+ findImportButton().vm.$emit('click');
await nextTick();
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 bd731dc3929..9bff77cd34a 100644
--- a/spec/frontend/import_entities/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/actions_spec.js
@@ -1,9 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { STATUSES } from '~/import_entities/constants';
+import actionsFactory from '~/import_entities/import_projects/store/actions';
+import { getImportTarget } from '~/import_entities/import_projects/store/getters';
import {
REQUEST_REPOS,
RECEIVE_REPOS_SUCCESS,
@@ -18,10 +19,9 @@ import {
SET_PAGE,
SET_FILTER,
} from '~/import_entities/import_projects/store/mutation_types';
-import actionsFactory from '~/import_entities/import_projects/store/actions';
-import { getImportTarget } from '~/import_entities/import_projects/store/getters';
import state from '~/import_entities/import_projects/store/state';
-import { STATUSES } from '~/import_entities/constants';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
jest.mock('~/flash');
diff --git a/spec/frontend/import_entities/import_projects/store/getters_spec.js b/spec/frontend/import_entities/import_projects/store/getters_spec.js
index f0ccffc19f2..55826b20ca3 100644
--- a/spec/frontend/import_entities/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/getters_spec.js
@@ -1,3 +1,4 @@
+import { STATUSES } from '~/import_entities/constants';
import {
isLoading,
isImportingAnyRepo,
@@ -6,7 +7,6 @@ import {
importAllCount,
getImportTarget,
} from '~/import_entities/import_projects/store/getters';
-import { STATUSES } from '~/import_entities/constants';
import state from '~/import_entities/import_projects/store/state';
const IMPORTED_REPO = {
diff --git a/spec/frontend/import_entities/import_projects/store/mutations_spec.js b/spec/frontend/import_entities/import_projects/store/mutations_spec.js
index 8b7ddffe6f4..e062d889325 100644
--- a/spec/frontend/import_entities/import_projects/store/mutations_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/mutations_spec.js
@@ -1,7 +1,7 @@
+import { STATUSES } from '~/import_entities/constants';
import * as types from '~/import_entities/import_projects/store/mutation_types';
import mutations from '~/import_entities/import_projects/store/mutations';
import getInitialState from '~/import_entities/import_projects/store/state';
-import { STATUSES } from '~/import_entities/constants';
describe('import_projects store mutations', () => {
let state;
diff --git a/spec/frontend/import_entities/import_projects/utils_spec.js b/spec/frontend/import_entities/import_projects/utils_spec.js
index 7d9c4b7137e..d705f0acbfe 100644
--- a/spec/frontend/import_entities/import_projects/utils_spec.js
+++ b/spec/frontend/import_entities/import_projects/utils_spec.js
@@ -1,9 +1,9 @@
+import { STATUSES } from '~/import_entities/constants';
import {
isProjectImportable,
isIncompatible,
getImportStatus,
} from '~/import_entities/import_projects/utils';
-import { STATUSES } from '~/import_entities/constants';
describe('import_projects utils', () => {
const COMPATIBLE_PROJECT = {
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index 8212776742c..df681658081 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -1,10 +1,6 @@
-import { mount } from '@vue/test-utils';
import { GlAlert, GlLoadingIcon, GlTable, GlAvatar, GlEmptyState } from '@gitlab/ui';
-import Tracking from '~/tracking';
-import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
+import { mount } from '@vue/test-utils';
import IncidentsList from '~/incidents/components/incidents_list.vue';
-import SeverityToken from '~/sidebar/components/severity/severity.vue';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import {
I18N,
TH_CREATED_AT_TEST_ID,
@@ -14,6 +10,10 @@ import {
trackIncidentCreateNewOptions,
trackIncidentListViewsOptions,
} from '~/incidents/constants';
+import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
+import SeverityToken from '~/sidebar/components/severity/severity.vue';
+import Tracking from '~/tracking';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import mockIncidents from '../mocks/incidents.json';
jest.mock('~/lib/utils/url_utility', () => ({
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
index 2b3c803be08..4398d568501 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
@@ -9,9 +9,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
<div
class="settings-header"
>
- <h4
- class="gl-my-3! gl-py-1"
- >
+ <h4>
Incidents
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 5010fc0bb5c..5476e895c68 100644
--- a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
+++ b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
@@ -1,9 +1,9 @@
import AxiosMockAdapter from 'axios-mock-adapter';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { ERROR_MSG } from '~/incidents_settings/constants';
+import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
-import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service';
-import { ERROR_MSG } from '~/incidents_settings/constants';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
jest.mock('~/flash');
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
index c80d9ad2e5b..ff40f1fa008 100644
--- a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
+++ b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import IncidentsSettingTabs from '~/incidents_settings/components/incidents_settings_tabs.vue';
describe('IncidentsSettingTabs', () => {
diff --git a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
index 50d0de8a753..2ffd1292ddc 100644
--- a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
+++ b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
@@ -1,6 +1,6 @@
+import { GlAlert, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
-import { GlAlert, GlModal } from '@gitlab/ui';
import PagerDutySettingsForm from '~/incidents_settings/components/pagerduty_form.vue';
describe('Alert integration settings form', () => {
diff --git a/spec/frontend/integrations/edit/components/active_checkbox_spec.js b/spec/frontend/integrations/edit/components/active_checkbox_spec.js
index 38bcb1e0aab..76fd6dd3a48 100644
--- a/spec/frontend/integrations/edit/components/active_checkbox_spec.js
+++ b/spec/frontend/integrations/edit/components/active_checkbox_spec.js
@@ -1,8 +1,7 @@
-import { mount } from '@vue/test-utils';
import { GlFormCheckbox } from '@gitlab/ui';
-import { createStore } from '~/integrations/edit/store';
-
+import { mount } from '@vue/test-utils';
import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue';
+import { createStore } from '~/integrations/edit/store';
describe('ActiveCheckbox', () => {
let wrapper;
diff --git a/spec/frontend/integrations/edit/components/confirmation_modal_spec.js b/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
index b570ab4e844..1c126f60c37 100644
--- a/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
+++ b/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
@@ -1,8 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
-import { createStore } from '~/integrations/edit/store';
-
+import { shallowMount } from '@vue/test-utils';
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
+import { createStore } from '~/integrations/edit/store';
describe('ConfirmationModal', () => {
let wrapper;
diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
index ecd03103992..aaca9fc4e62 100644
--- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js
+++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
describe('DynamicField', () => {
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index 97e77ac87ab..df855674804 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -1,38 +1,47 @@
import { shallowMount } from '@vue/test-utils';
+import { setHTMLFixture } from 'helpers/fixtures';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
-import { createStore } from '~/integrations/edit/store';
-import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
-import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue';
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
-import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue';
-import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
+import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
+import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
+import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
+import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
+import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
-import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { integrationLevels } from '~/integrations/edit/constants';
+import { createStore } from '~/integrations/edit/store';
describe('IntegrationForm', () => {
let wrapper;
- const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
- wrapper = shallowMount(IntegrationForm, {
- propsData: {},
- store: createStore({
- customState: { ...mockIntegrationProps, ...customStateProps },
- ...initialState,
+ const createComponent = ({
+ customStateProps = {},
+ featureFlags = {},
+ initialState = {},
+ props = {},
+ } = {}) => {
+ wrapper = extendedWrapper(
+ shallowMount(IntegrationForm, {
+ propsData: { ...props },
+ store: createStore({
+ customState: { ...mockIntegrationProps, ...customStateProps },
+ ...initialState,
+ }),
+ stubs: {
+ OverrideDropdown,
+ ActiveCheckbox,
+ ConfirmationModal,
+ JiraTriggerFields,
+ TriggerFields,
+ },
+ provide: {
+ glFeatures: featureFlags,
+ },
}),
- stubs: {
- OverrideDropdown,
- ActiveCheckbox,
- ConfirmationModal,
- JiraTriggerFields,
- TriggerFields,
- },
- provide: {
- glFeatures: featureFlags,
- },
- });
+ );
};
afterEach(() => {
@@ -63,7 +72,9 @@ describe('IntegrationForm', () => {
describe('showActive is false', () => {
it('does not render ActiveCheckbox', () => {
createComponent({
- showActive: false,
+ customStateProps: {
+ showActive: false,
+ },
});
expect(findActiveCheckbox().exists()).toBe(false);
@@ -73,7 +84,9 @@ describe('IntegrationForm', () => {
describe('integrationLevel is instance', () => {
it('renders ConfirmationModal', () => {
createComponent({
- integrationLevel: integrationLevels.INSTANCE,
+ customStateProps: {
+ integrationLevel: integrationLevels.INSTANCE,
+ },
});
expect(findConfirmationModal().exists()).toBe(true);
@@ -82,7 +95,9 @@ describe('IntegrationForm', () => {
describe('resetPath is empty', () => {
it('does not render ResetConfirmationModal and button', () => {
createComponent({
- integrationLevel: integrationLevels.INSTANCE,
+ customStateProps: {
+ integrationLevel: integrationLevels.INSTANCE,
+ },
});
expect(findResetButton().exists()).toBe(false);
@@ -93,8 +108,10 @@ describe('IntegrationForm', () => {
describe('resetPath is present', () => {
it('renders ResetConfirmationModal and button', () => {
createComponent({
- integrationLevel: integrationLevels.INSTANCE,
- resetPath: 'resetPath',
+ customStateProps: {
+ integrationLevel: integrationLevels.INSTANCE,
+ resetPath: 'resetPath',
+ },
});
expect(findResetButton().exists()).toBe(true);
@@ -106,7 +123,9 @@ describe('IntegrationForm', () => {
describe('integrationLevel is group', () => {
it('renders ConfirmationModal', () => {
createComponent({
- integrationLevel: integrationLevels.GROUP,
+ customStateProps: {
+ integrationLevel: integrationLevels.GROUP,
+ },
});
expect(findConfirmationModal().exists()).toBe(true);
@@ -115,7 +134,9 @@ describe('IntegrationForm', () => {
describe('resetPath is empty', () => {
it('does not render ResetConfirmationModal and button', () => {
createComponent({
- integrationLevel: integrationLevels.GROUP,
+ customStateProps: {
+ integrationLevel: integrationLevels.GROUP,
+ },
});
expect(findResetButton().exists()).toBe(false);
@@ -126,8 +147,10 @@ describe('IntegrationForm', () => {
describe('resetPath is present', () => {
it('renders ResetConfirmationModal and button', () => {
createComponent({
- integrationLevel: integrationLevels.GROUP,
- resetPath: 'resetPath',
+ customStateProps: {
+ integrationLevel: integrationLevels.GROUP,
+ resetPath: 'resetPath',
+ },
});
expect(findResetButton().exists()).toBe(true);
@@ -139,7 +162,9 @@ describe('IntegrationForm', () => {
describe('integrationLevel is project', () => {
it('does not render ConfirmationModal', () => {
createComponent({
- integrationLevel: 'project',
+ customStateProps: {
+ integrationLevel: 'project',
+ },
});
expect(findConfirmationModal().exists()).toBe(false);
@@ -147,8 +172,10 @@ describe('IntegrationForm', () => {
it('does not render ResetConfirmationModal and button', () => {
createComponent({
- integrationLevel: 'project',
- resetPath: 'resetPath',
+ customStateProps: {
+ integrationLevel: 'project',
+ resetPath: 'resetPath',
+ },
});
expect(findResetButton().exists()).toBe(false);
@@ -158,7 +185,9 @@ describe('IntegrationForm', () => {
describe('type is "slack"', () => {
beforeEach(() => {
- createComponent({ type: 'slack' });
+ createComponent({
+ customStateProps: { type: 'slack' },
+ });
});
it('does not render JiraTriggerFields', () => {
@@ -172,14 +201,19 @@ describe('IntegrationForm', () => {
describe('type is "jira"', () => {
it('renders JiraTriggerFields', () => {
- createComponent({ type: 'jira' });
+ createComponent({
+ customStateProps: { type: 'jira' },
+ });
expect(findJiraTriggerFields().exists()).toBe(true);
});
describe('featureFlag jiraIssuesIntegration is false', () => {
it('does not render JiraIssuesFields', () => {
- createComponent({ type: 'jira' }, { jiraIssuesIntegration: false });
+ createComponent({
+ customStateProps: { type: 'jira' },
+ featureFlags: { jiraIssuesIntegration: false },
+ });
expect(findJiraIssuesFields().exists()).toBe(false);
});
@@ -187,8 +221,10 @@ describe('IntegrationForm', () => {
describe('featureFlag jiraIssuesIntegration is true', () => {
it('renders JiraIssuesFields', () => {
- createComponent({ type: 'jira' }, { jiraIssuesIntegration: true });
-
+ createComponent({
+ customStateProps: { type: 'jira' },
+ featureFlags: { jiraIssuesIntegration: true },
+ });
expect(findJiraIssuesFields().exists()).toBe(true);
});
});
@@ -200,8 +236,10 @@ describe('IntegrationForm', () => {
const type = 'slack';
createComponent({
- triggerEvents: events,
- type,
+ customStateProps: {
+ triggerEvents: events,
+ type,
+ },
});
expect(findTriggerFields().exists()).toBe(true);
@@ -218,7 +256,9 @@ describe('IntegrationForm', () => {
];
createComponent({
- fields,
+ customStateProps: {
+ fields,
+ },
});
const dynamicFields = wrapper.findAll(DynamicField);
@@ -232,13 +272,11 @@ describe('IntegrationForm', () => {
describe('defaultState state is null', () => {
it('does not render OverrideDropdown', () => {
- createComponent(
- {},
- {},
- {
+ createComponent({
+ initialState: {
defaultState: null,
},
- );
+ });
expect(findOverrideDropdown().exists()).toBe(false);
});
@@ -246,18 +284,43 @@ describe('IntegrationForm', () => {
describe('defaultState state is an object', () => {
it('renders OverrideDropdown', () => {
- createComponent(
- {},
- {},
- {
+ createComponent({
+ initialState: {
defaultState: {
...mockIntegrationProps,
},
},
- );
+ });
expect(findOverrideDropdown().exists()).toBe(true);
});
});
+
+ describe('with `helpHtml` prop', () => {
+ const mockTestId = 'jest-help-html-test';
+
+ setHTMLFixture(`
+ <div data-testid="${mockTestId}">
+ <svg class="gl-icon">
+ <use></use>
+ </svg>
+ </div>
+ `);
+
+ it('renders `helpHtml`', async () => {
+ const mockHelpHtml = document.querySelector(`[data-testid="${mockTestId}"]`);
+
+ createComponent({
+ props: {
+ helpHtml: mockHelpHtml.outerHTML,
+ },
+ });
+
+ const helpHtml = wrapper.findByTestId(mockTestId);
+
+ expect(helpHtml.isVisible()).toBe(true);
+ expect(helpHtml.find('svg').isVisible()).toBe(true);
+ });
+ });
});
});
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 eaeed2703d1..3938e7c7c22 100644
--- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
@@ -1,20 +1,23 @@
-import { mount } from '@vue/test-utils';
-
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
+import eventHub from '~/integrations/edit/event_hub';
describe('JiraIssuesFields', () => {
let wrapper;
const defaultProps = {
- showJiraIssuesIntegration: true,
editProjectPath: '/edit',
+ showJiraIssuesIntegration: true,
+ showJiraVulnerabilitiesIntegration: true,
};
- const createComponent = (props) => {
+ const createComponent = ({ props, ...options } = {}) => {
wrapper = mount(JiraIssuesFields, {
propsData: { ...defaultProps, ...props },
+ stubs: ['jira-issue-creation-vulnerabilities'],
+ ...options,
});
};
@@ -28,11 +31,14 @@ describe('JiraIssuesFields', () => {
const findEnableCheckbox = () => wrapper.find(GlFormCheckbox);
const findProjectKey = () => wrapper.find(GlFormInput);
const expectedBannerText = 'This is a Premium feature';
+ const findJiraForVulnerabilities = () => wrapper.find('[data-testid="jira-for-vulnerabilities"]');
+ const setEnableCheckbox = async (isEnabled = true) =>
+ findEnableCheckbox().vm.$emit('input', isEnabled);
describe('template', () => {
describe('upgrade banner for non-Premium user', () => {
beforeEach(() => {
- createComponent({ initialProjectKey: '', showJiraIssuesIntegration: false });
+ createComponent({ props: { initialProjectKey: '', showJiraIssuesIntegration: false } });
});
it('shows upgrade banner', () => {
@@ -47,7 +53,7 @@ describe('JiraIssuesFields', () => {
describe('Enable Jira issues checkbox', () => {
beforeEach(() => {
- createComponent({ initialProjectKey: '' });
+ createComponent({ props: { initialProjectKey: '' } });
});
it('does not show upgrade banner', () => {
@@ -69,20 +75,16 @@ describe('JiraIssuesFields', () => {
});
describe('on enable issues', () => {
- it('enables project_key input', () => {
- findEnableCheckbox().vm.$emit('input', true);
+ it('enables project_key input', async () => {
+ await setEnableCheckbox(true);
- return wrapper.vm.$nextTick().then(() => {
- expect(findProjectKey().attributes('disabled')).toBeUndefined();
- });
+ expect(findProjectKey().attributes('disabled')).toBeUndefined();
});
- it('requires project_key input', () => {
- findEnableCheckbox().vm.$emit('input', true);
+ it('requires project_key input', async () => {
+ await setEnableCheckbox(true);
- return wrapper.vm.$nextTick().then(() => {
- expect(findProjectKey().attributes('required')).toBe('required');
- });
+ expect(findProjectKey().attributes('required')).toBe('required');
});
});
});
@@ -103,10 +105,60 @@ describe('JiraIssuesFields', () => {
});
it('does not contain warning when GitLab issues is disabled', () => {
- createComponent({ gitlabIssuesEnabled: false });
+ createComponent({ props: { gitlabIssuesEnabled: false } });
expect(wrapper.text()).not.toContain(expectedText);
});
});
+
+ describe('Vulnerabilities creation', () => {
+ beforeEach(() => {
+ createComponent({ provide: { glFeatures: { jiraForVulnerabilities: true } } });
+ });
+
+ it.each([true, false])(
+ 'shows the jira-vulnerabilities component correctly when jira issues enables is set to "%s"',
+ async (hasJiraIssuesEnabled) => {
+ await setEnableCheckbox(hasJiraIssuesEnabled);
+
+ expect(findJiraForVulnerabilities().exists()).toBe(hasJiraIssuesEnabled);
+ },
+ );
+
+ it('passes down the correct initial-issue-type-id value when value is empty', async () => {
+ await setEnableCheckbox(true);
+ expect(findJiraForVulnerabilities().attributes('initial-issue-type-id')).toBeUndefined();
+ });
+
+ it('passes down the correct initial-issue-type-id value when value is not empty', async () => {
+ const jiraIssueType = 'some-jira-issue-type';
+ wrapper.setProps({ initialVulnerabilitiesIssuetype: jiraIssueType });
+ await setEnableCheckbox(true);
+ expect(findJiraForVulnerabilities().attributes('initial-issue-type-id')).toBe(
+ jiraIssueType,
+ );
+ });
+
+ it('emits "getJiraIssueTypes" to the eventHub when the jira-vulnerabilities component requests to fetch issue types', async () => {
+ const eventHubEmitSpy = jest.spyOn(eventHub, '$emit');
+
+ await setEnableCheckbox(true);
+ await findJiraForVulnerabilities().vm.$emit('request-get-issue-types');
+
+ expect(eventHubEmitSpy).toHaveBeenCalledWith('getJiraIssueTypes');
+ });
+
+ describe('with "jiraForVulnerabilities" feature flag disabled', () => {
+ beforeEach(async () => {
+ createComponent({
+ provide: { glFeatures: { jiraForVulnerabilities: false } },
+ });
+ });
+
+ it('does not show section', () => {
+ expect(findJiraForVulnerabilities().exists()).toBe(false);
+ });
+ });
+ });
});
});
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 a69e8d6e163..c6e7ee44355 100644
--- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlFormCheckbox } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
describe('JiraTriggerFields', () => {
diff --git a/spec/frontend/integrations/edit/components/override_dropdown_spec.js b/spec/frontend/integrations/edit/components/override_dropdown_spec.js
index f312c456d5f..592f4514e45 100644
--- a/spec/frontend/integrations/edit/components/override_dropdown_spec.js
+++ b/spec/frontend/integrations/edit/components/override_dropdown_spec.js
@@ -1,9 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlLink } from '@gitlab/ui';
-import { createStore } from '~/integrations/edit/store';
-
-import { integrationLevels, overrideDropdownDescriptions } from '~/integrations/edit/constants';
+import { shallowMount } from '@vue/test-utils';
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
+import { integrationLevels, overrideDropdownDescriptions } from '~/integrations/edit/constants';
+import { createStore } from '~/integrations/edit/store';
describe('OverrideDropdown', () => {
let wrapper;
diff --git a/spec/frontend/integrations/edit/components/trigger_fields_spec.js b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
index 3fa1e5b5f5a..3e5326812b1 100644
--- a/spec/frontend/integrations/edit/components/trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
describe('TriggerFields', () => {
diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js
index 1ff881c265d..e2f4c138ece 100644
--- a/spec/frontend/integrations/edit/store/actions_spec.js
+++ b/spec/frontend/integrations/edit/store/actions_spec.js
@@ -1,6 +1,4 @@
import testAction from 'helpers/vuex_action_helper';
-import { refreshCurrentPage } from '~/lib/utils/url_utility';
-import createState from '~/integrations/edit/store/state';
import {
setOverride,
setIsSaving,
@@ -9,8 +7,13 @@ import {
requestResetIntegration,
receiveResetIntegrationSuccess,
receiveResetIntegrationError,
+ requestJiraIssueTypes,
+ receiveJiraIssueTypesSuccess,
+ receiveJiraIssueTypesError,
} from '~/integrations/edit/store/actions';
import * as types from '~/integrations/edit/store/mutation_types';
+import createState from '~/integrations/edit/store/state';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
@@ -70,4 +73,34 @@ describe('Integration form store actions', () => {
]);
});
});
+
+ describe('requestJiraIssueTypes', () => {
+ it('should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations', () => {
+ return testAction(requestJiraIssueTypes, null, state, [
+ { type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' },
+ { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true },
+ ]);
+ });
+ });
+
+ describe('receiveJiraIssueTypesSuccess', () => {
+ it('should commit SET_IS_LOADING_JIRA_ISSUE_TYPES and SET_JIRA_ISSUE_TYPES mutations', () => {
+ const issueTypes = ['issue', 'epic'];
+ return testAction(receiveJiraIssueTypesSuccess, issueTypes, state, [
+ { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: false },
+ { type: types.SET_JIRA_ISSUE_TYPES, payload: issueTypes },
+ ]);
+ });
+ });
+
+ describe('receiveJiraIssueTypesError', () => {
+ it('should commit SET_IS_LOADING_JIRA_ISSUE_TYPES, SET_JIRA_ISSUE_TYPES and SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE mutations', () => {
+ const errorMessage = 'something went wrong';
+ return testAction(receiveJiraIssueTypesError, errorMessage, state, [
+ { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: false },
+ { type: types.SET_JIRA_ISSUE_TYPES, payload: [] },
+ { type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: errorMessage },
+ ]);
+ });
+ });
});
diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js
index 7d4532a1059..ad7a887dff2 100644
--- a/spec/frontend/integrations/edit/store/getters_spec.js
+++ b/spec/frontend/integrations/edit/store/getters_spec.js
@@ -4,9 +4,9 @@ import {
isDisabled,
propsSource,
} from '~/integrations/edit/store/getters';
-import createState from '~/integrations/edit/store/state';
-import mutations from '~/integrations/edit/store/mutations';
import * as types from '~/integrations/edit/store/mutation_types';
+import mutations from '~/integrations/edit/store/mutations';
+import createState from '~/integrations/edit/store/state';
import { mockIntegrationProps } from '../mock_data';
describe('Integration form store getters', () => {
diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js
index 81f39adb87f..18faa2f6bba 100644
--- a/spec/frontend/integrations/edit/store/mutations_spec.js
+++ b/spec/frontend/integrations/edit/store/mutations_spec.js
@@ -1,6 +1,6 @@
+import * as types from '~/integrations/edit/store/mutation_types';
import mutations from '~/integrations/edit/store/mutations';
import createState from '~/integrations/edit/store/state';
-import * as types from '~/integrations/edit/store/mutation_types';
describe('Integration form store mutations', () => {
let state;
@@ -56,4 +56,30 @@ describe('Integration form store mutations', () => {
expect(state.isResetting).toBe(false);
});
});
+
+ describe(`${types.SET_JIRA_ISSUE_TYPES}`, () => {
+ it('sets jiraIssueTypes', () => {
+ const jiraIssueTypes = ['issue', 'epic'];
+ mutations[types.SET_JIRA_ISSUE_TYPES](state, jiraIssueTypes);
+
+ expect(state.jiraIssueTypes).toBe(jiraIssueTypes);
+ });
+ });
+
+ describe(`${types.SET_IS_LOADING_JIRA_ISSUE_TYPES}`, () => {
+ it.each([true, false])('sets isLoadingJiraIssueTypes to "%s"', (isLoading) => {
+ mutations[types.SET_IS_LOADING_JIRA_ISSUE_TYPES](state, isLoading);
+
+ expect(state.isLoadingJiraIssueTypes).toBe(isLoading);
+ });
+ });
+
+ describe(`${types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE}`, () => {
+ it('sets loadingJiraIssueTypesErrorMessage', () => {
+ const errorMessage = 'something went wrong';
+ mutations[types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE](state, errorMessage);
+
+ expect(state.loadingJiraIssueTypesErrorMessage).toBe(errorMessage);
+ });
+ });
});
diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js
index 4d0f4a1da71..6cd84836395 100644
--- a/spec/frontend/integrations/edit/store/state_spec.js
+++ b/spec/frontend/integrations/edit/store/state_spec.js
@@ -9,6 +9,9 @@ describe('Integration form state factory', () => {
isTesting: false,
isResetting: false,
override: false,
+ isLoadingJiraIssueTypes: false,
+ jiraIssueTypes: [],
+ loadingJiraIssueTypesErrorMessage: '',
});
});
diff --git a/spec/frontend/integrations/integration_settings_form_spec.js b/spec/frontend/integrations/integration_settings_form_spec.js
index bba851ad796..348b942703f 100644
--- a/spec/frontend/integrations/integration_settings_form_spec.js
+++ b/spec/frontend/integrations/integration_settings_form_spec.js
@@ -1,6 +1,6 @@
import MockAdaptor from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
+import axios from '~/lib/utils/axios_utils';
import toast from '~/vue_shared/plugins/global_toast';
jest.mock('~/vue_shared/plugins/global_toast');
@@ -132,4 +132,83 @@ describe('IntegrationSettingsForm', () => {
expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false);
});
});
+
+ describe('getJiraIssueTypes', () => {
+ let integrationSettingsForm;
+ let formData;
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdaptor(axios);
+
+ jest.spyOn(axios, 'put');
+
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ integrationSettingsForm.init();
+
+ // eslint-disable-next-line no-jquery/no-serialize
+ formData = integrationSettingsForm.$form.serialize();
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should always dispatch `requestJiraIssueTypes`', async () => {
+ const dispatchSpy = jest.fn();
+
+ mock.onPut(integrationSettingsForm.testEndPoint).networkError();
+
+ integrationSettingsForm.vue.$store = { dispatch: dispatchSpy };
+
+ await integrationSettingsForm.getJiraIssueTypes();
+
+ expect(dispatchSpy).toHaveBeenCalledWith('requestJiraIssueTypes');
+ });
+
+ it('should make an ajax request with provided `formData`', async () => {
+ await integrationSettingsForm.getJiraIssueTypes(formData);
+
+ expect(axios.put).toHaveBeenCalledWith(integrationSettingsForm.testEndPoint, formData);
+ });
+
+ it('should dispatch `receiveJiraIssueTypesSuccess` with the correct payload if ajax request is successful', async () => {
+ const mockData = ['ISSUE', 'EPIC'];
+ const dispatchSpy = jest.fn();
+
+ mock.onPut(integrationSettingsForm.testEndPoint).reply(200, {
+ error: false,
+ issuetypes: mockData,
+ });
+
+ integrationSettingsForm.vue.$store = { dispatch: dispatchSpy };
+
+ await integrationSettingsForm.getJiraIssueTypes(formData);
+
+ expect(dispatchSpy).toHaveBeenCalledWith('receiveJiraIssueTypesSuccess', mockData);
+ });
+
+ it.each(['something went wrong', undefined])(
+ 'should dispatch "receiveJiraIssueTypesError" with a message if the backend responds with error',
+ async (responseErrorMessage) => {
+ const defaultErrorMessage = 'Connection failed. Please check your settings.';
+ const expectedErrorMessage = responseErrorMessage || defaultErrorMessage;
+ const dispatchSpy = jest.fn();
+
+ mock.onPut(integrationSettingsForm.testEndPoint).reply(200, {
+ error: true,
+ message: responseErrorMessage,
+ });
+
+ integrationSettingsForm.vue.$store = { dispatch: dispatchSpy };
+
+ await integrationSettingsForm.getJiraIssueTypes(formData);
+
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ 'receiveJiraIssueTypesError',
+ expectedErrorMessage,
+ );
+ },
+ );
+ });
});
diff --git a/spec/frontend/invite_member/components/invite_member_modal_spec.js b/spec/frontend/invite_member/components/invite_member_modal_spec.js
index 1140c2a9475..4eff19402a8 100644
--- a/spec/frontend/invite_member/components/invite_member_modal_spec.js
+++ b/spec/frontend/invite_member/components/invite_member_modal_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink, GlModal } from '@gitlab/ui';
-import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
+import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
+import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import InviteMemberModal from '~/invite_member/components/invite_member_modal.vue';
const memberPath = 'member_path';
diff --git a/spec/frontend/invite_member/components/invite_member_trigger_spec.js b/spec/frontend/invite_member/components/invite_member_trigger_spec.js
index 57b8918e3da..67c312fd155 100644
--- a/spec/frontend/invite_member/components/invite_member_trigger_spec.js
+++ b/spec/frontend/invite_member/components/invite_member_trigger_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import InviteMemberTrigger from '~/invite_member/components/invite_member_trigger.vue';
import triggerProvides from './invite_member_trigger_mock_data';
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 fc039bdf6da..e310a00133c 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlDatepicker, GlSprintf, GlLink, GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
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 450d37a9748..18d6662d2d4 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
const displayText = 'Invite team members';
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 ff123a13ce7..a945b99bd54 100644
--- a/spec/frontend/invite_members/components/members_token_select_spec.js
+++ b/spec/frontend/invite_members/components/members_token_select_spec.js
@@ -1,8 +1,8 @@
+import { GlTokenSelector } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import { GlTokenSelector } from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
import { stubComponent } from 'helpers/stub_component';
+import waitForPromises from 'helpers/wait_for_promises';
import * as UserApi from '~/api/user_api';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js
new file mode 100644
index 00000000000..08a99f29479
--- /dev/null
+++ b/spec/frontend/issuable/components/issuable_by_email_spec.js
@@ -0,0 +1,164 @@
+import { GlModal, GlSprintf, GlFormInputGroup, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
+import httpStatus from '~/lib/utils/http_status';
+
+const initialEmail = 'user@gitlab.com';
+
+const mockToastShow = jest.fn();
+
+describe('IssuableByEmail', () => {
+ let wrapper;
+ let mockAxios;
+ let glModalDirective;
+
+ function createComponent(injectedProperties = {}) {
+ glModalDirective = jest.fn();
+
+ return extendedWrapper(
+ shallowMount(IssuableByEmail, {
+ stubs: {
+ GlModal,
+ GlSprintf,
+ GlFormInputGroup,
+ GlButton,
+ },
+ directives: {
+ glModal: {
+ bind(_, { value }) {
+ glModalDirective(value);
+ },
+ },
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
+ },
+ provide: {
+ issuableType: 'issue',
+ initialEmail,
+ ...injectedProperties,
+ },
+ }),
+ );
+ }
+
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mockAxios.restore();
+ });
+
+ const findFormInputGroup = () => wrapper.find(GlFormInputGroup);
+
+ const clickResetEmail = async () => {
+ wrapper.findByTestId('incoming-email-token-reset').vm.$emit('click');
+
+ await waitForPromises();
+ };
+
+ describe('modal button', () => {
+ it.each`
+ issuableType | buttonText
+ ${'issue'} | ${'Email a new issue to this project'}
+ ${'merge_request'} | ${'Email a new merge request to this project'}
+ `(
+ 'renders a link with "$buttonText" when type is "$issuableType"',
+ ({ issuableType, buttonText }) => {
+ wrapper = createComponent({ issuableType });
+ expect(wrapper.findByTestId('issuable-email-modal-btn').text()).toBe(buttonText);
+ },
+ );
+
+ it('opens the modal when the user clicks the button', () => {
+ wrapper = createComponent();
+
+ wrapper.findByTestId('issuable-email-modal-btn').vm.$emit('click');
+
+ expect(glModalDirective).toHaveBeenCalled();
+ });
+ });
+
+ describe('modal', () => {
+ it('renders a read-only email input field', () => {
+ wrapper = createComponent();
+
+ expect(findFormInputGroup().props('value')).toBe('user@gitlab.com');
+ });
+
+ it.each`
+ issuableType | subject | body
+ ${'issue'} | ${'Enter the issue title'} | ${'Enter the issue description'}
+ ${'merge_request'} | ${'Enter the merge request title'} | ${'Enter the merge request description'}
+ `('renders a mailto button when type is "$issuableType"', ({ issuableType, subject, body }) => {
+ wrapper = createComponent({
+ issuableType,
+ initialEmail,
+ });
+
+ expect(wrapper.findByTestId('mail-to-btn').attributes('href')).toBe(
+ `mailto:${initialEmail}?subject=${subject}&body=${body}`,
+ );
+ });
+
+ describe('reset email', () => {
+ const resetPath = 'gitlab-test/new_issuable_address?issuable_type=issue';
+
+ beforeEach(() => {
+ jest.spyOn(axios, 'put');
+ });
+ it('should send request to reset email token', async () => {
+ wrapper = createComponent({
+ issuableType: 'issue',
+ initialEmail,
+ resetPath,
+ });
+
+ await clickResetEmail();
+
+ expect(axios.put).toHaveBeenCalledWith(resetPath);
+ });
+
+ it('should update the email when the request succeeds', async () => {
+ mockAxios.onPut(resetPath).reply(httpStatus.OK, { new_address: 'foo@bar.com' });
+
+ wrapper = createComponent({
+ issuableType: 'issue',
+ initialEmail,
+ resetPath,
+ });
+
+ await clickResetEmail();
+
+ expect(findFormInputGroup().props('value')).toBe('foo@bar.com');
+ });
+
+ it('should show a toast message when the request fails', async () => {
+ mockAxios.onPut(resetPath).reply(httpStatus.NOT_FOUND, {});
+
+ wrapper = createComponent({
+ issuableType: 'issue',
+ initialEmail,
+ resetPath,
+ });
+
+ await clickResetEmail();
+
+ expect(mockToastShow).toHaveBeenCalledWith(
+ 'There was an error when reseting email token.',
+ { type: 'error' },
+ );
+ expect(findFormInputGroup().props('value')).toBe('user@gitlab.com');
+ });
+ });
+ });
+});
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 de2671816d8..173d12757e3 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,6 +1,6 @@
import { mount, shallowMount } from '@vue/test-utils';
-import { issuableTypesMap, linkedIssueTypesMap, PathIdSeparator } from '~/related_issues/constants';
import AddIssuableForm from '~/related_issues/components/add_issuable_form.vue';
+import { issuableTypesMap, linkedIssueTypesMap, PathIdSeparator } from '~/related_issues/constants';
const issuable1 = {
id: 200,
diff --git a/spec/frontend/issuable/related_issues/components/issue_token_spec.js b/spec/frontend/issuable/related_issues/components/issue_token_spec.js
index d5181d4a17a..d6aeacfe07a 100644
--- a/spec/frontend/issuable/related_issues/components/issue_token_spec.js
+++ b/spec/frontend/issuable/related_issues/components/issue_token_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import { PathIdSeparator } from '~/related_issues/constants';
import IssueToken from '~/related_issues/components/issue_token.vue';
+import { PathIdSeparator } from '~/related_issues/constants';
describe('IssueToken', () => {
const idKey = 200;
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 c0889b09adc..a450f912c4e 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 { shallowMount, mount } from '@vue/test-utils';
import { GlButton, GlIcon } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import {
issuable1,
issuable2,
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 93fe321957c..e5e3478dc59 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
@@ -6,11 +6,11 @@ import {
issuable1,
issuable2,
} from 'jest/vue_shared/components/issue/related_issuable_mock_data';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
-import relatedIssuesService from '~/related_issues/services/related_issues_service';
import { linkedIssueTypesMap } from '~/related_issues/constants';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
+import relatedIssuesService from '~/related_issues/services/related_issues_service';
jest.mock('~/flash');
diff --git a/spec/frontend/issuable_create/components/issuable_form_spec.js b/spec/frontend/issuable_create/components/issuable_form_spec.js
index e489d1dae3e..a074fddf091 100644
--- a/spec/frontend/issuable_create/components/issuable_form_spec.js
+++ b/spec/frontend/issuable_create/components/issuable_form_spec.js
@@ -1,11 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import IssuableForm from '~/issuable_create/components/issuable_form.vue';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
-import IssuableForm from '~/issuable_create/components/issuable_form.vue';
-
const createComponent = ({
descriptionPreviewPath = '/gitlab-org/gitlab-shell/preview_markdown',
descriptionHelpPath = '/help/user/markdown',
diff --git a/spec/frontend/issuable_list/components/issuable_item_spec.js b/spec/frontend/issuable_list/components/issuable_item_spec.js
index 3c01bf2d319..987acf559e3 100644
--- a/spec/frontend/issuable_list/components/issuable_item_spec.js
+++ b/spec/frontend/issuable_list/components/issuable_item_spec.js
@@ -1,6 +1,6 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink, GlLabel, GlIcon, GlFormCheckbox } from '@gitlab/ui';
-
+import { shallowMount } from '@vue/test-utils';
+import { useFakeDate } from 'helpers/fake_date';
import IssuableItem from '~/issuable_list/components/issuable_item.vue';
import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
@@ -18,14 +18,19 @@ const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots
slots,
});
+const MOCK_GITLAB_URL = 'http://0.0.0.0:3000';
+
describe('IssuableItem', () => {
+ // The mock data is dependent that this is after our default date
+ useFakeDate(2020, 11, 11);
+
const mockLabels = mockIssuable.labels.nodes;
const mockAuthor = mockIssuable.author;
const originalUrl = gon.gitlab_url;
let wrapper;
beforeEach(() => {
- gon.gitlab_url = 'http://0.0.0.0:3000';
+ gon.gitlab_url = MOCK_GITLAB_URL;
wrapper = createComponent();
});
@@ -70,11 +75,11 @@ describe('IssuableItem', () => {
describe('isIssuableUrlExternal', () => {
it.each`
- issuableWebUrl | urlType | returnValue
- ${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false}
- ${'http://0.0.0.0:3000/gitlab-org/gitlab-test/-/issues/1'} | ${'absolute and internal'} | ${false}
- ${'http://jira.atlassian.net/browse/IG-1'} | ${'external'} | ${true}
- ${'https://github.com/gitlabhq/gitlabhq/issues/1'} | ${'external'} | ${true}
+ issuableWebUrl | urlType | returnValue
+ ${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false}
+ ${`${MOCK_GITLAB_URL}/gitlab-org/gitlab-test/-/issues/1`} | ${'absolute and internal'} | ${false}
+ ${'http://jira.atlassian.net/browse/IG-1'} | ${'external'} | ${true}
+ ${'https://github.com/gitlabhq/gitlabhq/issues/1'} | ${'external'} | ${true}
`(
'returns $returnValue when `issuable.webUrl` is $urlType',
async ({ issuableWebUrl, returnValue }) => {
@@ -214,14 +219,32 @@ describe('IssuableItem', () => {
});
describe('template', () => {
- it('renders issuable title', () => {
- const titleEl = wrapper.find('[data-testid="issuable-title"]');
+ it.each`
+ gitlabWebUrl | webUrl | expectedHref | expectedTarget
+ ${undefined} | ${`${MOCK_GITLAB_URL}/issue`} | ${`${MOCK_GITLAB_URL}/issue`} | ${undefined}
+ ${undefined} | ${'https://jira.com/issue'} | ${'https://jira.com/issue'} | ${'_blank'}
+ ${'/gitlab-org/issue'} | ${'https://jira.com/issue'} | ${'/gitlab-org/issue'} | ${undefined}
+ `(
+ 'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`',
+ async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => {
+ wrapper.setProps({
+ issuable: {
+ ...mockIssuable,
+ webUrl,
+ gitlabWebUrl,
+ },
+ });
- expect(titleEl.exists()).toBe(true);
- expect(titleEl.find(GlLink).attributes('href')).toBe(mockIssuable.webUrl);
- expect(titleEl.find(GlLink).attributes('target')).not.toBeDefined();
- expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title);
- });
+ await wrapper.vm.$nextTick();
+
+ const titleEl = wrapper.find('[data-testid="issuable-title"]');
+
+ expect(titleEl.exists()).toBe(true);
+ expect(titleEl.find(GlLink).attributes('href')).toBe(expectedHref);
+ expect(titleEl.find(GlLink).attributes('target')).toBe(expectedTarget);
+ expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title);
+ },
+ );
it('renders checkbox when `showCheckbox` prop is true', async () => {
wrapper.setProps({
@@ -257,6 +280,23 @@ describe('IssuableItem', () => {
);
});
+ it('renders issuable confidential icon when issuable is confidential', async () => {
+ wrapper.setProps({
+ issuable: {
+ ...mockIssuable,
+ confidential: true,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ const confidentialEl = wrapper.find('[data-testid="issuable-title"]').find(GlIcon);
+
+ expect(confidentialEl.exists()).toBe(true);
+ expect(confidentialEl.props('name')).toBe('eye-slash');
+ expect(confidentialEl.attributes('title')).toBe('Confidential');
+ });
+
it('renders issuable reference', () => {
const referenceEl = wrapper.find('[data-testid="issuable-reference"]');
diff --git a/spec/frontend/issuable_list/components/issuable_list_root_spec.js b/spec/frontend/issuable_list/components/issuable_list_root_spec.js
index add5d9e8e2d..9c57233548c 100644
--- a/spec/frontend/issuable_list/components/issuable_list_root_spec.js
+++ b/spec/frontend/issuable_list/components/issuable_list_root_spec.js
@@ -1,11 +1,11 @@
-import { mount } from '@vue/test-utils';
import { GlSkeletonLoading, GlPagination } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
+import IssuableItem from '~/issuable_list/components/issuable_item.vue';
import IssuableListRoot from '~/issuable_list/components/issuable_list_root.vue';
import IssuableTabs from '~/issuable_list/components/issuable_tabs.vue';
-import IssuableItem from '~/issuable_list/components/issuable_item.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { mockIssuableListProps, mockIssuables } from '../mock_data';
diff --git a/spec/frontend/issuable_list/components/issuable_tabs_spec.js b/spec/frontend/issuable_list/components/issuable_tabs_spec.js
index 12611400084..3cc237b9ce9 100644
--- a/spec/frontend/issuable_list/components/issuable_tabs_spec.js
+++ b/spec/frontend/issuable_list/components/issuable_tabs_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlTab, GlBadge } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import IssuableTabs from '~/issuable_list/components/issuable_tabs.vue';
diff --git a/spec/frontend/issuable_show/components/issuable_body_spec.js b/spec/frontend/issuable_show/components/issuable_body_spec.js
index 4ffbbad4f37..bf166bea1e5 100644
--- a/spec/frontend/issuable_show/components/issuable_body_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_body_spec.js
@@ -1,10 +1,11 @@
import { shallowMount } from '@vue/test-utils';
+import { useFakeDate } from 'helpers/fake_date';
import IssuableBody from '~/issuable_show/components/issuable_body.vue';
-import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
import IssuableDescription from '~/issuable_show/components/issuable_description.vue';
import IssuableEditForm from '~/issuable_show/components/issuable_edit_form.vue';
+import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
@@ -35,6 +36,9 @@ const createComponent = (propsData = issuableBodyProps) =>
});
describe('IssuableBody', () => {
+ // Some assertions expect a date later than our default
+ useFakeDate(2020, 11, 11);
+
let wrapper;
beforeEach(() => {
@@ -98,11 +102,8 @@ describe('IssuableBody', () => {
it('renders issuable edit info', () => {
const editedEl = wrapper.find('small');
- const sanitizedText = editedEl.text().replace(/\n/g, ' ').replace(/\s+/g, ' ');
- expect(sanitizedText).toContain('Edited');
- expect(sanitizedText).toContain('ago');
- expect(sanitizedText).toContain(`by ${mockIssuable.updatedBy.name}`);
+ expect(editedEl.text()).toMatchInterpolatedText('Edited 3 months ago by Administrator');
});
it('renders issuable-edit-form when `editFormVisible` prop is true', async () => {
diff --git a/spec/frontend/issuable_show/components/issuable_description_spec.js b/spec/frontend/issuable_show/components/issuable_description_spec.js
index 1dd8348b098..29ecce1002d 100644
--- a/spec/frontend/issuable_show/components/issuable_description_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_description_spec.js
@@ -1,5 +1,5 @@
-import $ from 'jquery';
import { shallowMount } from '@vue/test-utils';
+import $ from 'jquery';
import IssuableDescription from '~/issuable_show/components/issuable_description.vue';
diff --git a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
index 522374f2e9c..184c9fe251c 100644
--- a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
-import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { shallowMount } from '@vue/test-utils';
import IssuableEditForm from '~/issuable_show/components/issuable_edit_form.vue';
import IssuableEventHub from '~/issuable_show/event_hub';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
diff --git a/spec/frontend/issuable_show/components/issuable_header_spec.js b/spec/frontend/issuable_show/components/issuable_header_spec.js
index f9c20ab04b8..2164caa40a8 100644
--- a/spec/frontend/issuable_show/components/issuable_header_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_header_spec.js
@@ -1,5 +1,6 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlAvatarLabeled } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IssuableHeader from '~/issuable_show/components/issuable_header.vue';
@@ -10,21 +11,23 @@ const issuableHeaderProps = {
...mockIssuableShowProps,
};
-const createComponent = (propsData = issuableHeaderProps) =>
- shallowMount(IssuableHeader, {
- propsData,
- slots: {
- 'status-badge': 'Open',
- 'header-actions': `
+const createComponent = (propsData = issuableHeaderProps, { stubs } = {}) =>
+ extendedWrapper(
+ shallowMount(IssuableHeader, {
+ propsData,
+ slots: {
+ 'status-badge': 'Open',
+ 'header-actions': `
<button class="js-close">Close issuable</button>
<a class="js-new" href="/gitlab-org/gitlab-shell/-/issues/new">New issuable</a>
`,
- },
- });
+ },
+ stubs,
+ }),
+ );
describe('IssuableHeader', () => {
let wrapper;
- const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
beforeEach(() => {
wrapper = createComponent();
@@ -63,7 +66,7 @@ describe('IssuableHeader', () => {
describe('template', () => {
it('renders issuable status icon and text', () => {
- const statusBoxEl = findByTestId('status');
+ const statusBoxEl = wrapper.findByTestId('status');
expect(statusBoxEl.exists()).toBe(true);
expect(statusBoxEl.find(GlIcon).props('name')).toBe(mockIssuableShowProps.statusIcon);
@@ -77,7 +80,7 @@ describe('IssuableHeader', () => {
await wrapper.vm.$nextTick();
- const blockedEl = findByTestId('blocked');
+ const blockedEl = wrapper.findByTestId('blocked');
expect(blockedEl.exists()).toBe(true);
expect(blockedEl.find(GlIcon).props('name')).toBe('lock');
@@ -90,7 +93,7 @@ describe('IssuableHeader', () => {
await wrapper.vm.$nextTick();
- const confidentialEl = findByTestId('confidential');
+ const confidentialEl = wrapper.findByTestId('confidential');
expect(confidentialEl.exists()).toBe(true);
expect(confidentialEl.find(GlIcon).props('name')).toBe('eye-slash');
@@ -105,7 +108,7 @@ describe('IssuableHeader', () => {
href: webUrl,
target: '_blank',
};
- const avatarEl = findByTestId('avatar');
+ const avatarEl = wrapper.findByTestId('avatar');
expect(avatarEl.exists()).toBe(true);
expect(avatarEl.attributes()).toMatchObject(avatarElAttrs);
expect(avatarEl.find(GlAvatarLabeled).attributes()).toMatchObject({
@@ -113,20 +116,46 @@ describe('IssuableHeader', () => {
src: avatarUrl,
label: name,
});
+ expect(avatarEl.find(GlAvatarLabeled).find(GlIcon).exists()).toBe(false);
});
it('renders sidebar toggle button', () => {
- const toggleButtonEl = findByTestId('sidebar-toggle');
+ const toggleButtonEl = wrapper.findByTestId('sidebar-toggle');
expect(toggleButtonEl.exists()).toBe(true);
expect(toggleButtonEl.props('icon')).toBe('chevron-double-lg-left');
});
it('renders header actions', () => {
- const actionsEl = findByTestId('header-actions');
+ const actionsEl = wrapper.findByTestId('header-actions');
expect(actionsEl.find('button.js-close').exists()).toBe(true);
expect(actionsEl.find('a.js-new').exists()).toBe(true);
});
+
+ describe('when author exists outside of GitLab', () => {
+ it("renders 'external-link' icon in avatar label", () => {
+ wrapper = createComponent(
+ {
+ ...issuableHeaderProps,
+ author: {
+ ...issuableHeaderProps.author,
+ webUrl: 'https://jira.com/test-user/author.jpg',
+ },
+ },
+ {
+ stubs: {
+ GlAvatarLabeled,
+ },
+ },
+ );
+
+ const avatarEl = wrapper.findComponent(GlAvatarLabeled);
+ const icon = avatarEl.find(GlIcon);
+
+ expect(icon.exists()).toBe(true);
+ expect(icon.props('name')).toBe('external-link');
+ });
+ });
});
});
diff --git a/spec/frontend/issuable_show/components/issuable_show_root_spec.js b/spec/frontend/issuable_show/components/issuable_show_root_spec.js
index 6cebfc150f9..3e3778492d2 100644
--- a/spec/frontend/issuable_show/components/issuable_show_root_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_show_root_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
+import IssuableBody from '~/issuable_show/components/issuable_body.vue';
+import IssuableHeader from '~/issuable_show/components/issuable_header.vue';
import IssuableShowRoot from '~/issuable_show/components/issuable_show_root.vue';
-import IssuableHeader from '~/issuable_show/components/issuable_header.vue';
-import IssuableBody from '~/issuable_show/components/issuable_body.vue';
import IssuableSidebar from '~/issuable_sidebar/components/issuable_sidebar_root.vue';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
diff --git a/spec/frontend/issuable_show/components/issuable_title_spec.js b/spec/frontend/issuable_show/components/issuable_title_spec.js
index e8621c763b3..df6fbdea76b 100644
--- a/spec/frontend/issuable_show/components/issuable_title_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_title_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
diff --git a/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js b/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js
index 7686dad4644..62a0016d67b 100644
--- a/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js
+++ b/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
import IssuableSidebarRoot from '~/issuable_sidebar/components/issuable_sidebar_root.vue';
diff --git a/spec/frontend/issuable_spec.js b/spec/frontend/issuable_spec.js
index 6712b8bfd34..9c8f1e04609 100644
--- a/spec/frontend/issuable_spec.js
+++ b/spec/frontend/issuable_spec.js
@@ -1,6 +1,3 @@
-import $ from 'jquery';
-import MockAdaptor from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
@@ -22,43 +19,4 @@ describe('Issuable', () => {
expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
});
});
-
- describe('resetIncomingEmailToken', () => {
- let mock;
-
- beforeEach(() => {
- const element = document.createElement('a');
- element.classList.add('incoming-email-token-reset');
- element.setAttribute('href', 'foo');
- document.body.appendChild(element);
-
- const input = document.createElement('input');
- input.setAttribute('id', 'issuable_email');
- document.body.appendChild(input);
-
- new IssuableIndex('issue_'); // eslint-disable-line no-new
-
- mock = new MockAdaptor(axios);
-
- mock.onPut('foo').reply(200, {
- new_address: 'testing123',
- });
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should send request to reset email token', (done) => {
- jest.spyOn(axios, 'put');
- document.querySelector('.incoming-email-token-reset').click();
-
- setImmediate(() => {
- expect(axios.put).toHaveBeenCalledWith('foo');
- expect($('#issuable_email').val()).toBe('testing123');
-
- done();
- });
- });
- });
});
diff --git a/spec/frontend/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js
index d0dde256edd..39083b3d8fb 100644
--- a/spec/frontend/issuable_suggestions/components/item_spec.js
+++ b/spec/frontend/issuable_suggestions/components/item_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlTooltip, GlLink, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
-import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import Suggestion from '~/issuable_suggestions/components/item.vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import mockData from '../mock_data';
describe('Issuable suggestions suggestion component', () => {
diff --git a/spec/frontend/issue_show/components/app_spec.js b/spec/frontend/issue_show/components/app_spec.js
index ec2055ca7d1..9e1bc8242fe 100644
--- a/spec/frontend/issue_show/components/app_spec.js
+++ b/spec/frontend/issue_show/components/app_spec.js
@@ -2,11 +2,15 @@ import { GlIntersectionObserver } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
-import axios from '~/lib/utils/axios_utils';
-import { visitUrl } from '~/lib/utils/url_utility';
import '~/behaviors/markdown/render_gfm';
import IssuableApp from '~/issue_show/components/app.vue';
+import DescriptionComponent from '~/issue_show/components/description.vue';
+import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
+import PinnedLinks from '~/issue_show/components/pinned_links.vue';
+import { IssuableStatus, IssuableStatusText } from '~/issue_show/constants';
import eventHub from '~/issue_show/event_hub';
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
import {
appProps,
initialRequest,
@@ -14,10 +18,6 @@ import {
secondRequest,
zoomMeetingUrl,
} from '../mock_data';
-import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
-import DescriptionComponent from '~/issue_show/components/description.vue';
-import PinnedLinks from '~/issue_show/components/pinned_links.vue';
-import { IssuableStatus, IssuableStatusText } from '~/issue_show/constants';
function formatText(text) {
return text.trim().replace(/\s\s+/g, ' ');
@@ -423,7 +423,7 @@ describe('Issuable output', () => {
});
it('shows the form if template names request is successful', () => {
- const mockData = [{ name: 'Bug' }];
+ const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }];
mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
return wrapper.vm.requestTemplatesAndShowForm().then(() => {
diff --git a/spec/frontend/issue_show/components/description_spec.js b/spec/frontend/issue_show/components/description_spec.js
index de53d98e6be..d59a257a2be 100644
--- a/spec/frontend/issue_show/components/description_spec.js
+++ b/spec/frontend/issue_show/components/description_spec.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
-import mountComponent from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import mountComponent from 'helpers/vue_mount_component_helper';
import Description from '~/issue_show/components/description.vue';
import TaskList from '~/task_list';
import { descriptionProps as props } from '../mock_data';
diff --git a/spec/frontend/issue_show/components/fields/description_spec.js b/spec/frontend/issue_show/components/fields/description_spec.js
index 96c81c419d0..a50be30cf4c 100644
--- a/spec/frontend/issue_show/components/fields/description_spec.js
+++ b/spec/frontend/issue_show/components/fields/description_spec.js
@@ -1,70 +1,70 @@
-import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import DescriptionField from '~/issue_show/components/fields/description.vue';
import eventHub from '~/issue_show/event_hub';
-import Store from '~/issue_show/stores';
-import descriptionField from '~/issue_show/components/fields/description.vue';
-import { keyboardDownEvent } from '../../helpers';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
describe('Description field component', () => {
- let vm;
- let store;
-
- beforeEach((done) => {
- const Component = Vue.extend(descriptionField);
- const el = document.createElement('div');
- store = new Store({
- titleHtml: '',
- descriptionHtml: '',
- issuableRef: '',
- });
- store.formState.description = 'test';
-
- document.body.appendChild(el);
+ let wrapper;
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ const findTextarea = () => wrapper.find({ ref: 'textarea' });
- vm = new Component({
- el,
+ const mountComponent = (description = 'test') =>
+ shallowMount(DescriptionField, {
+ attachTo: document.body,
propsData: {
markdownPreviewPath: '/',
markdownDocsPath: '/',
- formState: store.formState,
+ formState: {
+ description,
+ },
+ },
+ stubs: {
+ MarkdownField,
},
- }).$mount();
+ });
+
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit');
+ });
- Vue.nextTick(done);
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
it('renders markdown field with description', () => {
- expect(vm.$el.querySelector('.md-area textarea').value).toBe('test');
+ wrapper = mountComponent();
+
+ expect(findTextarea().element.value).toBe('test');
});
- it('renders markdown field with a markdown description', (done) => {
- store.formState.description = '**test**';
+ it('renders markdown field with a markdown description', () => {
+ const markdown = '**test**';
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.md-area textarea').value).toBe('**test**');
+ wrapper = mountComponent(markdown);
- done();
- });
+ expect(findTextarea().element.value).toBe(markdown);
});
it('focuses field when mounted', () => {
- expect(document.activeElement).toBe(vm.$refs.textarea);
+ wrapper = mountComponent();
+
+ expect(document.activeElement).toBe(findTextarea().element);
});
it('triggers update with meta+enter', () => {
- vm.$el.querySelector('.md-area textarea').dispatchEvent(keyboardDownEvent(13, true));
+ wrapper = mountComponent();
- expect(eventHub.$emit).toHaveBeenCalled();
+ findTextarea().trigger('keydown.enter', { metaKey: true });
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
});
it('triggers update with ctrl+enter', () => {
- vm.$el.querySelector('.md-area textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+ wrapper = mountComponent();
- expect(eventHub.$emit).toHaveBeenCalled();
- });
+ findTextarea().trigger('keydown.enter', { ctrlKey: true });
- it('has a ref named `textarea`', () => {
- expect(vm.$refs.textarea).not.toBeNull();
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
});
});
diff --git a/spec/frontend/issue_show/components/fields/description_template_spec.js b/spec/frontend/issue_show/components/fields/description_template_spec.js
index 9ebab31f1ad..1193d4f8add 100644
--- a/spec/frontend/issue_show/components/fields/description_template_spec.js
+++ b/spec/frontend/issue_show/components/fields/description_template_spec.js
@@ -14,8 +14,10 @@ describe('Issue description template component', () => {
vm = new Component({
propsData: {
formState,
- issuableTemplates: [{ name: 'test' }],
+ issuableTemplates: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
+ projectId: 1,
projectPath: '/',
+ namespacePath: '/',
projectNamespace: '/',
},
}).$mount();
@@ -23,7 +25,7 @@ describe('Issue description template component', () => {
it('renders templates as JSON array in data attribute', () => {
expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe(
- '[{"name":"test"}]',
+ '[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]',
);
});
diff --git a/spec/frontend/issue_show/components/fields/title_spec.js b/spec/frontend/issue_show/components/fields/title_spec.js
index 99e8658b89f..783ce9eb76c 100644
--- a/spec/frontend/issue_show/components/fields/title_spec.js
+++ b/spec/frontend/issue_show/components/fields/title_spec.js
@@ -1,48 +1,42 @@
-import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import TitleField from '~/issue_show/components/fields/title.vue';
import eventHub from '~/issue_show/event_hub';
-import Store from '~/issue_show/stores';
-import titleField from '~/issue_show/components/fields/title.vue';
-import { keyboardDownEvent } from '../../helpers';
describe('Title field component', () => {
- let vm;
- let store;
+ let wrapper;
- beforeEach(() => {
- const Component = Vue.extend(titleField);
- store = new Store({
- titleHtml: '',
- descriptionHtml: '',
- issuableRef: '',
- });
- store.formState.title = 'test';
+ const findInput = () => wrapper.find({ ref: 'input' });
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit');
- vm = new Component({
+ wrapper = shallowMount(TitleField, {
propsData: {
- formState: store.formState,
+ formState: {
+ title: 'test',
+ },
},
- }).$mount();
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
it('renders form control with formState title', () => {
- expect(vm.$el.querySelector('.form-control').value).toBe('test');
+ expect(findInput().element.value).toBe('test');
});
it('triggers update with meta+enter', () => {
- vm.$el.querySelector('.form-control').dispatchEvent(keyboardDownEvent(13, true));
+ findInput().trigger('keydown.enter', { metaKey: true });
- expect(eventHub.$emit).toHaveBeenCalled();
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
});
it('triggers update with ctrl+enter', () => {
- vm.$el.querySelector('.form-control').dispatchEvent(keyboardDownEvent(13, false, true));
-
- expect(eventHub.$emit).toHaveBeenCalled();
- });
+ findInput().trigger('keydown.enter', { ctrlKey: true });
- it('has a ref named `input`', () => {
- expect(vm.$refs.input).not.toBeNull();
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
});
});
diff --git a/spec/frontend/issue_show/components/form_spec.js b/spec/frontend/issue_show/components/form_spec.js
index 4e123f606f6..4a8ec3cf66a 100644
--- a/spec/frontend/issue_show/components/form_spec.js
+++ b/spec/frontend/issue_show/components/form_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
-import formComponent from '~/issue_show/components/form.vue';
import Autosave from '~/autosave';
+import formComponent from '~/issue_show/components/form.vue';
import eventHub from '~/issue_show/event_hub';
jest.mock('~/autosave');
@@ -19,6 +19,7 @@ describe('Inline edit form component', () => {
markdownPreviewPath: '/',
markdownDocsPath: '/',
projectPath: '/',
+ projectId: 1,
projectNamespace: '/',
};
@@ -42,7 +43,11 @@ describe('Inline edit form component', () => {
});
it('renders template selector when templates exists', () => {
- createComponent({ issuableTemplates: ['test'] });
+ createComponent({
+ issuableTemplates: [
+ { name: 'test', id: 'test', project_path: 'test', namespace_path: 'test' },
+ ],
+ });
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
});
diff --git a/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js b/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js
index 112cb4d4c3a..6758e6192b8 100644
--- a/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js
+++ b/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js
@@ -1,6 +1,6 @@
+import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import merge from 'lodash/merge';
-import { GlLink } from '@gitlab/ui';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
diff --git a/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js b/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js
index 416870d1408..f46b6ba6f54 100644
--- a/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js
+++ b/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js
@@ -1,15 +1,15 @@
+import { GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import merge from 'lodash/merge';
-import { GlTab } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
-import INVALID_URL from '~/lib/utils/invalid_url';
-import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
-import { descriptionProps } from '../../mock_data';
+import { trackIncidentDetailsViewsOptions } from '~/incidents/constants';
import DescriptionComponent from '~/issue_show/components/description.vue';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar.vue';
-import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
+import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
+import INVALID_URL from '~/lib/utils/invalid_url';
import Tracking from '~/tracking';
-import { trackIncidentDetailsViewsOptions } from '~/incidents/constants';
+import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
+import { descriptionProps } from '../../mock_data';
const mockAlert = {
__typename: 'AlertManagementAlert',
diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js
index 2d140fd068a..3fe1f9fd6d9 100644
--- a/spec/frontend/issue_show/components/pinned_links_spec.js
+++ b/spec/frontend/issue_show/components/pinned_links_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import PinnedLinks from '~/issue_show/components/pinned_links.vue';
import { STATUS_PAGE_PUBLISHED, JOIN_ZOOM_MEETING } from '~/issue_show/constants';
diff --git a/spec/frontend/issue_show/components/title_spec.js b/spec/frontend/issue_show/components/title_spec.js
index c274048fdd5..78880a7f540 100644
--- a/spec/frontend/issue_show/components/title_spec.js
+++ b/spec/frontend/issue_show/components/title_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
-import Store from '~/issue_show/stores';
import titleComponent from '~/issue_show/components/title.vue';
import eventHub from '~/issue_show/event_hub';
+import Store from '~/issue_show/stores';
describe('Title component', () => {
let vm;
diff --git a/spec/frontend/issue_show/helpers.js b/spec/frontend/issue_show/helpers.js
deleted file mode 100644
index 7ca6a22929d..00000000000
--- a/spec/frontend/issue_show/helpers.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export const keyboardDownEvent = (code, metaKey = false, ctrlKey = false) => {
- const e = new CustomEvent('keydown');
-
- e.keyCode = code;
- e.metaKey = metaKey;
- e.ctrlKey = ctrlKey;
-
- return e;
-};
diff --git a/spec/frontend/issue_show/issue_spec.js b/spec/frontend/issue_show/issue_spec.js
index 818f501882b..9cb7059dd7f 100644
--- a/spec/frontend/issue_show/issue_spec.js
+++ b/spec/frontend/issue_show/issue_spec.js
@@ -1,11 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import { initIssuableApp } from '~/issue_show/issue';
import * as parseData from '~/issue_show/utils/parse_data';
-import { appProps } from './mock_data';
+import axios from '~/lib/utils/axios_utils';
import createStore from '~/notes/stores';
+import { appProps } from './mock_data';
const mock = new MockAdapter(axios);
mock.onGet().reply(200);
diff --git a/spec/frontend/issue_show/mock_data.js b/spec/frontend/issue_show/mock_data.js
index 5a31a550088..fd08c95b454 100644
--- a/spec/frontend/issue_show/mock_data.js
+++ b/spec/frontend/issue_show/mock_data.js
@@ -52,6 +52,7 @@ export const appProps = {
markdownDocsPath: '/',
projectNamespace: '/',
projectPath: '/',
+ projectId: 1,
issuableTemplateNamesPath: '/issuable-templates-path',
zoomMeetingUrl,
publishedIncidentUrl,
diff --git a/spec/frontend/issue_spec.js b/spec/frontend/issue_spec.js
index 303267e784f..fb6caef41e2 100644
--- a/spec/frontend/issue_spec.js
+++ b/spec/frontend/issue_spec.js
@@ -1,7 +1,7 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
+import $ from 'jquery';
import Issue from '~/issue';
+import axios from '~/lib/utils/axios_utils';
import '~/lib/utils/text_utility';
describe('Issue', () => {
diff --git a/spec/frontend/issues_list/components/issuable_spec.js b/spec/frontend/issues_list/components/issuable_spec.js
index b47a84ad7f6..a8bf124373b 100644
--- a/spec/frontend/issues_list/components/issuable_spec.js
+++ b/spec/frontend/issues_list/components/issuable_spec.js
@@ -1,20 +1,26 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf, GlLabel, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
-import initUserPopovers from '~/user_popovers';
+import Issuable from '~/issues_list/components/issuable.vue';
+import { isScopedLabel } from '~/lib/utils/common_utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import { mergeUrlParams } from '~/lib/utils/url_utility';
-import Issuable from '~/issues_list/components/issuable.vue';
+import initUserPopovers from '~/user_popovers';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { simpleIssue, testAssignees, testLabels } from '../issuable_list_test_data';
-import { isScopedLabel } from '~/lib/utils/common_utils';
jest.mock('~/user_popovers');
-const TEST_NOW = '2019-08-28T20:03:04.713Z';
-const TEST_MONTH_AGO = '2019-07-28';
-const TEST_MONTH_LATER = '2019-09-30';
+const TODAY = new Date();
+
+const createTestDateFromDelta = (timeDelta) =>
+ formatDate(new Date(TODAY.getTime() + timeDelta), 'yyyy-mm-dd');
+
+// TODO: Encapsulate date helpers https://gitlab.com/gitlab-org/gitlab/-/issues/320883
+const MONTHS_IN_MS = 1000 * 60 * 60 * 24 * 31;
+const TEST_MONTH_AGO = createTestDateFromDelta(-MONTHS_IN_MS);
+const TEST_MONTH_LATER = createTestDateFromDelta(MONTHS_IN_MS);
const DATE_FORMAT = 'mmm d, yyyy';
const TEST_USER_NAME = 'Tyler Durden';
const TEST_BASE_URL = `${TEST_HOST}/issues`;
@@ -26,16 +32,8 @@ const TEST_MILESTONE = {
const TEXT_CLOSED = 'CLOSED';
const TEST_META_COUNT = 100;
-// Use FixedDate so that time sensitive info in snapshots don't fail
-class FixedDate extends Date {
- constructor(date = TEST_NOW) {
- super(date);
- }
-}
-
describe('Issuable component', () => {
let issuable;
- let DateOrig;
let wrapper;
const factory = (props = {}, scopedLabelsAvailable = false) => {
@@ -63,15 +61,6 @@ describe('Issuable component', () => {
wrapper = null;
});
- beforeAll(() => {
- DateOrig = window.Date;
- window.Date = FixedDate;
- });
-
- afterAll(() => {
- window.Date = DateOrig;
- });
-
const checkExists = (findFn) => () => findFn().exists();
const hasIcon = (iconName, iconWrapper = wrapper) =>
iconWrapper.findAll(GlIcon).wrappers.some((icon) => icon.props('name') === iconName);
diff --git a/spec/frontend/issues_list/components/issuables_list_app_spec.js b/spec/frontend/issues_list/components/issuables_list_app_spec.js
index 9d603099ff7..fe3d2114463 100644
--- a/spec/frontend/issues_list/components/issuables_list_app_spec.js
+++ b/spec/frontend/issues_list/components/issuables_list_app_spec.js
@@ -1,19 +1,19 @@
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import { shallowMount } from '@vue/test-utils';
import {
GlEmptyState,
GlPagination,
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
} from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import { deprecatedCreateFlash as flash } from '~/flash';
-import IssuablesListApp from '~/issues_list/components/issuables_list_app.vue';
import Issuable from '~/issues_list/components/issuable.vue';
-import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
-import issueablesEventBus from '~/issues_list/eventhub';
+import IssuablesListApp from '~/issues_list/components/issuables_list_app.vue';
import { PAGE_SIZE, PAGE_SIZE_MANUAL, RELATIVE_POSITION } from '~/issues_list/constants';
+import issueablesEventBus from '~/issues_list/eventhub';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
jest.mock('~/flash');
jest.mock('~/issues_list/eventhub');
@@ -591,5 +591,75 @@ describe('Issuables list component', () => {
expect(findFilteredSearchBar().props('initialFilterValue')).toEqual(['free text']);
});
});
+
+ describe('on filter search', () => {
+ beforeEach(() => {
+ factory({ type: 'jira' });
+
+ window.history.pushState = jest.fn();
+ });
+
+ afterEach(() => {
+ window.history.pushState.mockRestore();
+ });
+
+ const emitOnFilter = (filter) => findFilteredSearchBar().vm.$emit('onFilter', filter);
+
+ describe('empty filter', () => {
+ const mockFilter = [];
+
+ it('updates URL with correct params', () => {
+ emitOnFilter(mockFilter);
+
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ {},
+ '',
+ `${TEST_LOCATION}?state=opened`,
+ );
+ });
+ });
+
+ describe('filter with search term', () => {
+ const mockFilter = [
+ {
+ type: 'filtered-search-term',
+ value: { data: 'free' },
+ },
+ ];
+
+ it('updates URL with correct params', () => {
+ emitOnFilter(mockFilter);
+
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ {},
+ '',
+ `${TEST_LOCATION}?state=opened&search=free`,
+ );
+ });
+ });
+
+ describe('filter with multiple search terms', () => {
+ const mockFilter = [
+ {
+ type: 'filtered-search-term',
+ value: { data: 'free' },
+ },
+ {
+ type: 'filtered-search-term',
+ value: { data: 'text' },
+ },
+ ];
+
+ it('updates URL with correct params', () => {
+ emitOnFilter(mockFilter);
+
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ {},
+ '',
+ `${TEST_LOCATION}?state=opened&search=free+text`,
+ );
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/jira_connect/api_spec.js b/spec/frontend/jira_connect/api_spec.js
index 8fecbee9ca7..240a57c7917 100644
--- a/spec/frontend/jira_connect/api_spec.js
+++ b/spec/frontend/jira_connect/api_spec.js
@@ -1,9 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
+import { addSubscription, removeSubscription, fetchGroups } from '~/jira_connect/api';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
-import { addSubscription, removeSubscription, fetchGroups } from '~/jira_connect/api';
-
describe('JiraConnect API', () => {
let mock;
let response;
@@ -14,7 +13,7 @@ describe('JiraConnect API', () => {
const mockJwt = 'jwt';
const mockResponse = { success: true };
- const tokenSpy = jest.fn().mockReturnValue(mockJwt);
+ const tokenSpy = jest.fn((callback) => callback(mockJwt));
window.AP = {
context: {
diff --git a/spec/frontend/jira_connect/components/app_spec.js b/spec/frontend/jira_connect/components/app_spec.js
index be990d5061c..d11b66b2089 100644
--- a/spec/frontend/jira_connect/components/app_spec.js
+++ b/spec/frontend/jira_connect/components/app_spec.js
@@ -1,19 +1,20 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
+import { GlAlert, GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { GlAlert } from '@gitlab/ui';
+
import JiraConnectApp from '~/jira_connect/components/app.vue';
import createStore from '~/jira_connect/store';
import { SET_ERROR_MESSAGE } from '~/jira_connect/store/mutation_types';
-Vue.use(Vuex);
+jest.mock('~/jira_connect/api');
describe('JiraConnectApp', () => {
let wrapper;
let store;
const findAlert = () => wrapper.findComponent(GlAlert);
+ const findGlButton = () => wrapper.findComponent(GlButton);
+ const findGlModal = () => wrapper.findComponent(GlModal);
const findHeader = () => wrapper.findByTestId('new-jira-connect-ui-heading');
const findHeaderText = () => findHeader().text();
@@ -44,6 +45,33 @@ describe('JiraConnectApp', () => {
expect(findHeaderText()).toBe('Linked namespaces');
});
+ describe('when user is not logged in', () => {
+ beforeEach(() => {
+ createComponent({
+ provide: {
+ glFeatures: { newJiraConnectUi: true },
+ usersPath: '/users',
+ },
+ });
+ });
+
+ it('renders "Sign in" button', () => {
+ expect(findGlButton().text()).toBe('Sign in to add namespaces');
+ expect(findGlModal().exists()).toBe(false);
+ });
+ });
+
+ describe('when user is logged in', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders "Add" button and modal', () => {
+ expect(findGlButton().text()).toBe('Add namespace');
+ expect(findGlModal().exists()).toBe(true);
+ });
+ });
+
describe('newJiraConnectUi is false', () => {
it('does not render new UI', () => {
createComponent({
diff --git a/spec/frontend/jira_connect/components/groups_list_item_spec.js b/spec/frontend/jira_connect/components/groups_list_item_spec.js
index 77577c53cf4..bb247534aca 100644
--- a/spec/frontend/jira_connect/components/groups_list_item_spec.js
+++ b/spec/frontend/jira_connect/components/groups_list_item_spec.js
@@ -1,27 +1,37 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlAvatar } from '@gitlab/ui';
+import { GlAvatar, GlButton } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { mockGroup1 } from '../mock_data';
+import waitForPromises from 'helpers/wait_for_promises';
+import * as JiraConnectApi from '~/jira_connect/api';
import GroupsListItem from '~/jira_connect/components/groups_list_item.vue';
+import { mockGroup1 } from '../mock_data';
describe('GroupsListItem', () => {
let wrapper;
+ const mockSubscriptionPath = 'subscriptionPath';
- const createComponent = () => {
+ const reloadSpy = jest.fn();
+
+ global.AP = {
+ navigator: {
+ reload: reloadSpy,
+ },
+ };
+
+ const createComponent = ({ mountFn = shallowMount } = {}) => {
wrapper = extendedWrapper(
- shallowMount(GroupsListItem, {
+ mountFn(GroupsListItem, {
propsData: {
group: mockGroup1,
},
+ provide: {
+ subscriptionsPath: mockSubscriptionPath,
+ },
}),
);
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -30,17 +40,82 @@ describe('GroupsListItem', () => {
const findGlAvatar = () => wrapper.find(GlAvatar);
const findGroupName = () => wrapper.findByTestId('group-list-item-name');
const findGroupDescription = () => wrapper.findByTestId('group-list-item-description');
+ const findLinkButton = () => wrapper.find(GlButton);
+ const clickLinkButton = () => findLinkButton().trigger('click');
- it('renders group avatar', () => {
- expect(findGlAvatar().exists()).toBe(true);
- expect(findGlAvatar().props('src')).toBe(mockGroup1.avatar_url);
- });
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders group avatar', () => {
+ expect(findGlAvatar().exists()).toBe(true);
+ expect(findGlAvatar().props('src')).toBe(mockGroup1.avatar_url);
+ });
+
+ it('renders group name', () => {
+ expect(findGroupName().text()).toBe(mockGroup1.full_name);
+ });
- it('renders group name', () => {
- expect(findGroupName().text()).toBe(mockGroup1.full_name);
+ it('renders group description', () => {
+ expect(findGroupDescription().text()).toBe(mockGroup1.description);
+ });
+
+ it('renders Link button', () => {
+ expect(findLinkButton().exists()).toBe(true);
+ expect(findLinkButton().text()).toBe('Link');
+ });
});
- it('renders group description', () => {
- expect(findGroupDescription().text()).toBe(mockGroup1.description);
+ describe('on Link button click', () => {
+ let addSubscriptionSpy;
+
+ beforeEach(() => {
+ createComponent({ mountFn: mount });
+
+ addSubscriptionSpy = jest.spyOn(JiraConnectApi, 'addSubscription').mockResolvedValue();
+ });
+
+ it('sets button to loading and sends request', async () => {
+ expect(findLinkButton().props('loading')).toBe(false);
+
+ clickLinkButton();
+
+ await wrapper.vm.$nextTick();
+
+ expect(findLinkButton().props('loading')).toBe(true);
+
+ expect(addSubscriptionSpy).toHaveBeenCalledWith(mockSubscriptionPath, mockGroup1.full_path);
+ });
+
+ describe('when request is successful', () => {
+ it('reloads the page', async () => {
+ clickLinkButton();
+
+ await waitForPromises();
+
+ expect(reloadSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('when request has errors', () => {
+ const mockErrorMessage = 'error message';
+ const mockError = { response: { data: { error: mockErrorMessage } } };
+
+ beforeEach(() => {
+ addSubscriptionSpy = jest
+ .spyOn(JiraConnectApi, 'addSubscription')
+ .mockRejectedValue(mockError);
+ });
+
+ it('emits `error` event', async () => {
+ clickLinkButton();
+
+ await waitForPromises();
+
+ expect(reloadSpy).not.toHaveBeenCalled();
+ expect(wrapper.emitted('error')[0][0]).toBe(mockErrorMessage);
+ });
+ });
});
});
diff --git a/spec/frontend/jira_connect/components/groups_list_spec.js b/spec/frontend/jira_connect/components/groups_list_spec.js
index 94f158e6344..5c645eccc0e 100644
--- a/spec/frontend/jira_connect/components/groups_list_spec.js
+++ b/spec/frontend/jira_connect/components/groups_list_spec.js
@@ -1,5 +1,5 @@
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlLoadingIcon } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import { fetchGroups } from '~/jira_connect/api';
@@ -28,6 +28,7 @@ describe('GroupsList', () => {
wrapper = null;
});
+ const findGlAlert = () => wrapper.find(GlAlert);
const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findAllItems = () => wrapper.findAll(GroupsListItem);
const findFirstItem = () => findAllItems().at(0);
@@ -45,6 +46,18 @@ describe('GroupsList', () => {
});
});
+ describe('error fetching groups', () => {
+ it('renders error message', async () => {
+ fetchGroups.mockRejectedValue();
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe('Failed to load namespaces. Please try again.');
+ });
+ });
+
describe('no groups returned', () => {
it('renders empty state', async () => {
fetchGroups.mockResolvedValue(mockEmptyResponse);
@@ -57,15 +70,28 @@ describe('GroupsList', () => {
});
describe('with groups returned', () => {
- it('renders groups list', async () => {
+ beforeEach(async () => {
fetchGroups.mockResolvedValue({ data: [mockGroup1, mockGroup2] });
createComponent();
await waitForPromises();
+ });
+ it('renders groups list', () => {
expect(findAllItems().length).toBe(2);
expect(findFirstItem().props('group')).toBe(mockGroup1);
expect(findSecondItem().props('group')).toBe(mockGroup2);
});
+
+ it('shows error message on $emit from item', async () => {
+ const errorMessage = 'error message';
+
+ findFirstItem().vm.$emit('error', errorMessage);
+
+ await wrapper.vm.$nextTick();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toContain(errorMessage);
+ });
});
});
diff --git a/spec/frontend/jira_connect/index_spec.js b/spec/frontend/jira_connect/index_spec.js
new file mode 100644
index 00000000000..eb54fe6476f
--- /dev/null
+++ b/spec/frontend/jira_connect/index_spec.js
@@ -0,0 +1,56 @@
+import waitForPromises from 'helpers/wait_for_promises';
+import { initJiraConnect } from '~/jira_connect';
+import { removeSubscription } from '~/jira_connect/api';
+
+jest.mock('~/jira_connect/api', () => ({
+ removeSubscription: jest.fn().mockResolvedValue(),
+ getLocation: jest.fn().mockResolvedValue('test/location'),
+}));
+
+describe('initJiraConnect', () => {
+ window.AP = {
+ navigator: {
+ reload: jest.fn(),
+ },
+ };
+
+ beforeEach(async () => {
+ setFixtures(`
+ <a class="js-jira-connect-sign-in" href="https://gitlab.com">Sign In</a>
+ <a class="js-jira-connect-sign-in" href="https://gitlab.com">Another Sign In</a>
+
+ <a href="https://gitlab.com/sub1" class="js-jira-connect-remove-subscription">Remove</a>
+ <a href="https://gitlab.com/sub2" class="js-jira-connect-remove-subscription">Remove</a>
+ <a href="https://gitlab.com/sub3" class="js-jira-connect-remove-subscription">Remove</a>
+ `);
+
+ await initJiraConnect();
+ });
+
+ describe('Sign in links', () => {
+ it('have `return_to` query parameter', () => {
+ Array.from(document.querySelectorAll('.js-jira-connect-sign-in')).forEach((el) => {
+ expect(el.href).toContain('return_to=test/location');
+ });
+ });
+ });
+
+ describe('`remove subscription` buttons', () => {
+ describe('on click', () => {
+ it('calls `removeSubscription`', () => {
+ Array.from(document.querySelectorAll('.js-jira-connect-remove-subscription')).forEach(
+ (removeSubscriptionButton) => {
+ removeSubscriptionButton.dispatchEvent(new Event('click'));
+
+ waitForPromises();
+
+ expect(removeSubscription).toHaveBeenCalledWith(removeSubscriptionButton.href);
+ expect(removeSubscription).toHaveBeenCalledTimes(1);
+
+ removeSubscription.mockClear();
+ },
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jira_connect/mock_data.js b/spec/frontend/jira_connect/mock_data.js
index 31565912489..22255fabc3d 100644
--- a/spec/frontend/jira_connect/mock_data.js
+++ b/spec/frontend/jira_connect/mock_data.js
@@ -3,6 +3,7 @@ export const mockGroup1 = {
avatar_url: 'avatar.png',
name: 'Gitlab Org',
full_name: 'Gitlab Org',
+ full_path: 'gitlab-org',
description: 'Open source software to collaborate on code',
};
@@ -11,5 +12,6 @@ export const mockGroup2 = {
avatar_url: 'avatar.png',
name: 'Gitlab Com',
full_name: 'Gitlab Com',
+ full_path: 'gitlab-com',
description: 'For GitLab company related projects',
};
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 00fb8f5435e..7a550d85204 100644
--- a/spec/frontend/jira_import/components/jira_import_form_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_form_spec.js
@@ -1,11 +1,21 @@
-import { GlAlert, GlButton, GlDropdown, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlFormSelect,
+ GlLabel,
+ GlSearchBoxByType,
+ GlTable,
+} from '@gitlab/ui';
import { getByRole } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import getJiraUserMappingMutation from '~/jira_import/queries/get_jira_user_mapping.mutation.graphql';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
+import searchProjectMembersQuery from '~/jira_import/queries/search_project_members.query.graphql';
+import axios from '~/lib/utils/axios_utils';
import {
imports,
issuesPath,
@@ -19,6 +29,7 @@ import {
describe('JiraImportForm', () => {
let axiosMock;
let mutateSpy;
+ let querySpy;
let wrapper;
const currentUsername = 'mrgitlab';
@@ -72,6 +83,7 @@ describe('JiraImportForm', () => {
$apollo: {
loading,
mutate,
+ query: querySpy,
},
},
currentUsername,
@@ -79,19 +91,21 @@ describe('JiraImportForm', () => {
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
- mutateSpy = jest.fn(() =>
- Promise.resolve({
- data: {
- jiraImportStart: { errors: [] },
- jiraImportUsers: { jiraUsers: [], errors: [] },
- },
- }),
- );
+ mutateSpy = jest.fn().mockResolvedValue({
+ data: {
+ jiraImportStart: { errors: [] },
+ jiraImportUsers: { jiraUsers: [], errors: [] },
+ },
+ });
+ querySpy = jest.fn().mockResolvedValue({
+ data: { project: { projectMembers: { nodes: [] } } },
+ });
});
afterEach(() => {
axiosMock.restore();
mutateSpy.mockRestore();
+ querySpy.mockRestore();
wrapper.destroy();
wrapper = null;
});
@@ -236,6 +250,53 @@ describe('JiraImportForm', () => {
});
});
+ describe('member search', () => {
+ describe('when searching for a member', () => {
+ beforeEach(() => {
+ querySpy = jest.fn().mockResolvedValue({
+ data: {
+ project: {
+ projectMembers: {
+ nodes: [
+ {
+ user: {
+ id: 7,
+ name: 'Frederic Chopin',
+ username: 'fchopin',
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ wrapper = mountComponent({ mountFunction: mount });
+
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', 'fred');
+ });
+
+ it('makes a GraphQL call', () => {
+ const queryArgument = {
+ query: searchProjectMembersQuery,
+ variables: {
+ fullPath: projectPath,
+ search: 'fred',
+ },
+ };
+
+ expect(querySpy).toHaveBeenCalledWith(expect.objectContaining(queryArgument));
+ });
+
+ it('updates the user list', () => {
+ expect(getUserDropdown().findAll(GlDropdownItem)).toHaveLength(1);
+ expect(getUserDropdown().find(GlDropdownItem).text()).toContain(
+ 'fchopin (Frederic Chopin)',
+ );
+ });
+ });
+ });
+
describe('buttons', () => {
describe('"Continue" button', () => {
it('is shown', () => {
diff --git a/spec/frontend/jira_import/mock_data.js b/spec/frontend/jira_import/mock_data.js
index 51dd939283e..8dc8ce08f49 100644
--- a/spec/frontend/jira_import/mock_data.js
+++ b/spec/frontend/jira_import/mock_data.js
@@ -1,6 +1,6 @@
import getJiraImportDetailsQuery from '~/jira_import/queries/get_jira_import_details.query.graphql';
-import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
import { userMappingsPageSize } from '~/jira_import/utils/constants';
+import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
export const fullPath = 'gitlab-org/gitlab-test';
diff --git a/spec/frontend/jira_import/utils/jira_import_utils_spec.js b/spec/frontend/jira_import/utils/jira_import_utils_spec.js
index 0992c9e8d16..9696d95f8c4 100644
--- a/spec/frontend/jira_import/utils/jira_import_utils_spec.js
+++ b/spec/frontend/jira_import/utils/jira_import_utils_spec.js
@@ -1,4 +1,5 @@
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issues_list/constants';
import {
calculateJiraImportLabel,
extractJiraProjectsOptions,
@@ -8,7 +9,6 @@ import {
setFinishedAlertHideMap,
shouldShowFinishedAlert,
} from '~/jira_import/utils/jira_import_utils';
-import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issues_list/constants';
useLocalStorageSpy();
diff --git a/spec/frontend/jobs/components/artifacts_block_spec.js b/spec/frontend/jobs/components/artifacts_block_spec.js
index 134463c6763..0c7c0a6c311 100644
--- a/spec/frontend/jobs/components/artifacts_block_spec.js
+++ b/spec/frontend/jobs/components/artifacts_block_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import { getTimeago } from '~/lib/utils/datetime_utility';
import ArtifactsBlock from '~/jobs/components/artifacts_block.vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
describe('Artifacts block', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/erased_block_spec.js b/spec/frontend/jobs/components/erased_block_spec.js
index b3e1d28eb16..057df20ccc2 100644
--- a/spec/frontend/jobs/components/erased_block_spec.js
+++ b/spec/frontend/jobs/components/erased_block_spec.js
@@ -1,7 +1,7 @@
-import { mount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
-import { getTimeago } from '~/lib/utils/datetime_utility';
+import { mount } from '@vue/test-utils';
import ErasedBlock from '~/jobs/components/erased_block.vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
describe('Erased block', () => {
let wrapper;
@@ -10,6 +10,8 @@ describe('Erased block', () => {
const timeago = getTimeago();
const formattedDate = timeago.format(erasedAt);
+ const findLink = () => wrapper.find(GlLink);
+
const createComponent = (props) => {
wrapper = mount(ErasedBlock, {
propsData: props,
@@ -32,7 +34,7 @@ describe('Erased block', () => {
});
it('renders username and link', () => {
- expect(wrapper.find(GlLink).attributes('href')).toEqual('gitlab.com/root');
+ expect(findLink().attributes('href')).toEqual('gitlab.com/root');
expect(wrapper.text().trim()).toContain('Job has been erased by');
expect(wrapper.text().trim()).toContain('root');
diff --git a/spec/frontend/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js
index 657687b5e2a..2974e91e46d 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job_app_spec.js
@@ -1,18 +1,18 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
+import EmptyState from '~/jobs/components/empty_state.vue';
+import EnvironmentsBlock from '~/jobs/components/environments_block.vue';
+import ErasedBlock from '~/jobs/components/erased_block.vue';
import JobApp from '~/jobs/components/job_app.vue';
import Sidebar from '~/jobs/components/sidebar.vue';
import StuckBlock from '~/jobs/components/stuck_block.vue';
import UnmetPrerequisitesBlock from '~/jobs/components/unmet_prerequisites_block.vue';
-import EnvironmentsBlock from '~/jobs/components/environments_block.vue';
-import ErasedBlock from '~/jobs/components/erased_block.vue';
-import EmptyState from '~/jobs/components/empty_state.vue';
import createStore from '~/jobs/store';
+import axios from '~/lib/utils/axios_utils';
import job from '../mock_data';
describe('Job App', () => {
@@ -34,7 +34,6 @@ describe('Job App', () => {
const props = {
artifactHelpUrl: 'help/artifact',
- runnerHelpUrl: 'help/runner',
deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners',
variablesSettingsUrl: 'settings/ci-cd/variables',
diff --git a/spec/frontend/jobs/components/job_container_item_spec.js b/spec/frontend/jobs/components/job_container_item_spec.js
index af7ce100d83..36038b69e64 100644
--- a/spec/frontend/jobs/components/job_container_item_spec.js
+++ b/spec/frontend/jobs/components/job_container_item_spec.js
@@ -1,78 +1,80 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { GlIcon, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import JobContainerItem from '~/jobs/components/job_container_item.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
import job from '../mock_data';
describe('JobContainerItem', () => {
+ let wrapper;
const delayedJobFixture = getJSONFixture('jobs/delayed.json');
- const Component = Vue.extend(JobContainerItem);
- let vm;
+
+ const findCiIconComponent = () => wrapper.findComponent(CiIcon);
+ const findGlIconComponent = () => wrapper.findComponent(GlIcon);
+
+ function createComponent(jobData = {}, props = { isActive: false, retried: false }) {
+ wrapper = shallowMount(JobContainerItem, {
+ propsData: {
+ job: {
+ ...jobData,
+ retried: props.retried,
+ },
+ isActive: props.isActive,
+ },
+ });
+ }
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- const sharedTests = () => {
+ describe('when a job is not active and not retried', () => {
+ beforeEach(() => {
+ createComponent(job);
+ });
+
it('displays a status icon', () => {
- expect(vm.$el).toHaveSpriteIcon(job.status.icon);
+ const ciIcon = findCiIconComponent();
+
+ expect(ciIcon.props('status')).toBe(job.status);
});
it('displays the job name', () => {
- expect(vm.$el.innerText).toContain(job.name);
+ expect(wrapper.text()).toContain(job.name);
});
it('displays a link to the job', () => {
- const link = vm.$el.querySelector('.js-job-link');
+ const link = wrapper.findComponent(GlLink);
- expect(link.href).toBe(job.status.details_path);
+ expect(link.attributes('href')).toBe(job.status.details_path);
});
- };
-
- describe('when a job is not active and not retied', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- job,
- isActive: false,
- });
- });
-
- sharedTests();
});
describe('when a job is active', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
- job,
- isActive: true,
- });
+ createComponent(job, { isActive: true });
});
- sharedTests();
+ it('displays an arrow sprite icon', () => {
+ const icon = findGlIconComponent();
- it('displays an arrow', () => {
- expect(vm.$el).toHaveSpriteIcon('arrow-right');
+ expect(icon.props('name')).toBe('arrow-right');
});
});
describe('when a job is retried', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
- job: {
- ...job,
- retried: true,
- },
- isActive: false,
- });
+ createComponent(job, { isActive: false, retried: true });
});
- sharedTests();
+ it('displays a retry icon', () => {
+ const icon = findGlIconComponent();
- it('displays an icon', () => {
- expect(vm.$el).toHaveSpriteIcon('retry');
+ expect(icon.props('name')).toBe('retry');
});
});
- describe('for delayed job', () => {
+ describe('for a delayed job', () => {
beforeEach(() => {
const remainingMilliseconds = 1337000;
jest
@@ -80,22 +82,16 @@ describe('JobContainerItem', () => {
.mockImplementation(
() => new Date(delayedJobFixture.scheduled_at).getTime() - remainingMilliseconds,
);
+
+ createComponent(delayedJobFixture);
});
- it('displays remaining time in tooltip', (done) => {
- vm = mountComponent(Component, {
- job: delayedJobFixture,
- isActive: false,
- });
-
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-job-link').getAttribute('title')).toEqual(
- 'delayed job - delayed manual action (00:22:17)',
- );
- })
- .then(done)
- .catch(done.fail);
+ it('displays remaining time in tooltip', async () => {
+ await wrapper.vm.$nextTick();
+
+ const link = wrapper.findComponent(GlLink);
+
+ expect(link.attributes('title')).toMatch('delayed job - delayed manual action (00:22:17)');
});
});
});
diff --git a/spec/frontend/jobs/components/job_sidebar_details_container_spec.js b/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
index bc0d455c309..2b56bd2d558 100644
--- a/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
+++ b/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import SidebarJobDetailsContainer from '~/jobs/components/sidebar_job_details_container.vue';
import DetailRow from '~/jobs/components/sidebar_detail_row.vue';
+import SidebarJobDetailsContainer from '~/jobs/components/sidebar_job_details_container.vue';
import createStore from '~/jobs/store';
import job from '../mock_data';
@@ -116,14 +116,5 @@ describe('Job Sidebar Details Container', () => {
expect(findJobTimeout().exists()).toBe(false);
});
-
- it('should pass the help URL', async () => {
- const helpUrl = 'fakeUrl';
- const props = { runnerHelpUrl: helpUrl };
- createWrapper({ props });
- await store.dispatch('receiveJobSuccess', { metadata: { timeout_human_readable } });
-
- expect(findJobTimeout().props('helpUrl')).toBe(helpUrl);
- });
});
});
diff --git a/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js b/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
index 4bf697ab7cc..8fc5b071e54 100644
--- a/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
+++ b/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
@@ -1,8 +1,8 @@
import { GlButton, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import job from '../mock_data';
import JobsSidebarRetryButton from '~/jobs/components/job_sidebar_retry_button.vue';
import createStore from '~/jobs/store';
+import job from '../mock_data';
describe('Job Sidebar Retry Button', () => {
let store;
diff --git a/spec/frontend/jobs/components/log/line_header_spec.js b/spec/frontend/jobs/components/log/line_header_spec.js
index bb90949b1f4..9763e2f437b 100644
--- a/spec/frontend/jobs/components/log/line_header_spec.js
+++ b/spec/frontend/jobs/components/log/line_header_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
+import DurationBadge from '~/jobs/components/log/duration_badge.vue';
import LineHeader from '~/jobs/components/log/line_header.vue';
import LineNumber from '~/jobs/components/log/line_number.vue';
-import DurationBadge from '~/jobs/components/log/duration_badge.vue';
describe('Job Log Header Line', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/log/log_spec.js b/spec/frontend/jobs/components/log/log_spec.js
index f662ffa1780..b7aff1f3e3b 100644
--- a/spec/frontend/jobs/components/log/log_spec.js
+++ b/spec/frontend/jobs/components/log/log_spec.js
@@ -1,7 +1,7 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { logLinesParser } from '~/jobs/store/utils';
import Log from '~/jobs/components/log/log.vue';
+import { logLinesParser } from '~/jobs/store/utils';
import { jobLog } from './mock_data';
describe('Job Log', () => {
diff --git a/spec/frontend/jobs/components/manual_variables_form_spec.js b/spec/frontend/jobs/components/manual_variables_form_spec.js
index f6c37407e2b..7172a319876 100644
--- a/spec/frontend/jobs/components/manual_variables_form_spec.js
+++ b/spec/frontend/jobs/components/manual_variables_form_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import Form from '~/jobs/components/manual_variables_form.vue';
const localVue = createLocalVue();
diff --git a/spec/frontend/jobs/components/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/sidebar_detail_row_spec.js
index 42d11266dad..bae4d6cf837 100644
--- a/spec/frontend/jobs/components/sidebar_detail_row_spec.js
+++ b/spec/frontend/jobs/components/sidebar_detail_row_spec.js
@@ -1,61 +1,55 @@
-import Vue from 'vue';
-import sidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue';
+import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import SidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue';
describe('Sidebar detail row', () => {
- let SidebarDetailRow;
- let vm;
+ let wrapper;
- beforeEach(() => {
- SidebarDetailRow = Vue.extend(sidebarDetailRow);
- });
+ const title = 'this is the title';
+ const value = 'this is the value';
+ const helpUrl = '/help/ci/runners/README.html';
- afterEach(() => {
- vm.$destroy();
- });
+ const findHelpLink = () => wrapper.findComponent(GlLink);
- it('should render no title', () => {
- vm = new SidebarDetailRow({
+ const createComponent = (props) => {
+ wrapper = shallowMount(SidebarDetailRow, {
propsData: {
- value: 'this is the value',
+ ...props,
},
- }).$mount();
+ });
+ };
- expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('this is the value');
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
- beforeEach(() => {
- vm = new SidebarDetailRow({
- propsData: {
- title: 'this is the title',
- value: 'this is the value',
- },
- }).$mount();
- });
+ describe('with title/value and without helpUrl', () => {
+ beforeEach(() => {
+ createComponent({ title, value });
+ });
- it('should render provided title and value', () => {
- expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual(
- 'this is the title: this is the value',
- );
- });
+ it('should render the provided title and value', () => {
+ expect(wrapper.text()).toBe(`${title}: ${value}`);
+ });
- describe('when helpUrl not provided', () => {
- it('should not render help', () => {
- expect(vm.$el.querySelector('.help-button')).toBeNull();
+ it('should not render the help link', () => {
+ expect(findHelpLink().exists()).toBe(false);
});
});
describe('when helpUrl provided', () => {
beforeEach(() => {
- vm = new SidebarDetailRow({
- propsData: {
- helpUrl: 'help url',
- value: 'foo',
- },
- }).$mount();
+ createComponent({
+ helpUrl,
+ title,
+ value,
+ });
});
- it('should render help', () => {
- expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url');
+ it('should render the help link', () => {
+ expect(findHelpLink().exists()).toBe(true);
+ expect(findHelpLink().attributes('href')).toBe(helpUrl);
});
});
});
diff --git a/spec/frontend/jobs/components/sidebar_spec.js b/spec/frontend/jobs/components/sidebar_spec.js
index 22d555ffec7..5a2e699137d 100644
--- a/spec/frontend/jobs/components/sidebar_spec.js
+++ b/spec/frontend/jobs/components/sidebar_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import Sidebar, { forwardDeploymentFailureModalId } from '~/jobs/components/sidebar.vue';
-import StagesDropdown from '~/jobs/components/stages_dropdown.vue';
-import JobsContainer from '~/jobs/components/jobs_container.vue';
import JobRetryForwardDeploymentModal from '~/jobs/components/job_retry_forward_deployment_modal.vue';
import JobRetryButton from '~/jobs/components/job_sidebar_retry_button.vue';
+import JobsContainer from '~/jobs/components/jobs_container.vue';
+import Sidebar, { forwardDeploymentFailureModalId } from '~/jobs/components/sidebar.vue';
+import StagesDropdown from '~/jobs/components/stages_dropdown.vue';
import createStore from '~/jobs/store';
import job, { jobsInStage } from '../mock_data';
diff --git a/spec/frontend/jobs/components/trigger_block_spec.js b/spec/frontend/jobs/components/trigger_block_spec.js
index 16ea276ee4a..e0eb873dc2f 100644
--- a/spec/frontend/jobs/components/trigger_block_spec.js
+++ b/spec/frontend/jobs/components/trigger_block_spec.js
@@ -1,100 +1,86 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import component from '~/jobs/components/trigger_block.vue';
+import { GlButton, GlTable } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import TriggerBlock from '~/jobs/components/trigger_block.vue';
describe('Trigger block', () => {
- const Component = Vue.extend(component);
- let vm;
+ let wrapper;
+
+ const findRevealButton = () => wrapper.find(GlButton);
+ const findVariableTable = () => wrapper.find(GlTable);
+ const findShortToken = () => wrapper.find('[data-testid="trigger-short-token"]');
+ const findVariableValue = (index) =>
+ wrapper.findAll('[data-testid="trigger-build-value"]').at(index);
+ const findVariableKey = (index) => wrapper.findAll('[data-testid="trigger-build-key"]').at(index);
+
+ const createComponent = (props) => {
+ wrapper = mount(TriggerBlock, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- describe('with short token', () => {
+ describe('with short token and no variables', () => {
it('renders short token', () => {
- vm = mountComponent(Component, {
+ createComponent({
trigger: {
short_token: '0a666b2',
+ variables: [],
},
});
- expect(vm.$el.querySelector('.js-short-token').textContent).toContain('0a666b2');
+ expect(findShortToken().text()).toContain('0a666b2');
});
});
- describe('without short token', () => {
+ describe('without variables or short token', () => {
+ beforeEach(() => {
+ createComponent({ trigger: { variables: [] } });
+ });
+
it('does not render short token', () => {
- vm = mountComponent(Component, { trigger: {} });
+ expect(findShortToken().exists()).toBe(false);
+ });
- expect(vm.$el.querySelector('.js-short-token')).toBeNull();
+ it('does not render variables', () => {
+ expect(findRevealButton().exists()).toBe(false);
+ expect(findVariableTable().exists()).toBe(false);
});
});
describe('with variables', () => {
describe('hide/reveal variables', () => {
- it('should toggle variables on click', (done) => {
- vm = mountComponent(Component, {
+ it('should toggle variables on click', async () => {
+ const hiddenValue = '••••••';
+ const gcsVar = { key: 'UPLOAD_TO_GCS', value: 'false', public: false };
+ const s3Var = { key: 'UPLOAD_TO_S3', value: 'true', public: false };
+
+ createComponent({
trigger: {
- short_token: 'bd7e',
- variables: [
- { key: 'UPLOAD_TO_GCS', value: 'false', public: false },
- { key: 'UPLOAD_TO_S3', value: 'true', public: false },
- ],
+ variables: [gcsVar, s3Var],
},
});
- vm.$el.querySelector('.js-reveal-variables').click();
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull();
- expect(vm.$el.querySelector('.js-reveal-variables').textContent.trim()).toEqual(
- 'Hide values',
- );
-
- expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
- 'UPLOAD_TO_GCS',
- );
+ expect(findRevealButton().text()).toBe('Reveal values');
- expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('false');
- expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
- 'UPLOAD_TO_S3',
- );
+ expect(findVariableValue(0).text()).toBe(hiddenValue);
+ expect(findVariableValue(1).text()).toBe(hiddenValue);
- expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('true');
+ expect(findVariableKey(0).text()).toBe(gcsVar.key);
+ expect(findVariableKey(1).text()).toBe(s3Var.key);
- vm.$el.querySelector('.js-reveal-variables').click();
- })
- .then(vm.$nextTick)
- .then(() => {
- expect(vm.$el.querySelector('.js-reveal-variables').textContent.trim()).toEqual(
- 'Reveal values',
- );
+ await findRevealButton().trigger('click');
- expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
- 'UPLOAD_TO_GCS',
- );
+ expect(findRevealButton().text()).toBe('Hide values');
- expect(vm.$el.querySelector('.js-build-value').textContent).toContain('••••••');
-
- expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
- 'UPLOAD_TO_S3',
- );
-
- expect(vm.$el.querySelector('.js-build-value').textContent).toContain('••••••');
- })
- .then(done)
- .catch(done.fail);
+ expect(findVariableValue(0).text()).toBe(gcsVar.value);
+ expect(findVariableValue(1).text()).toBe(s3Var.value);
});
});
});
-
- describe('without variables', () => {
- it('does not render variables', () => {
- vm = mountComponent(Component, { trigger: {} });
-
- expect(vm.$el.querySelector('.js-reveal-variables')).toBeNull();
- expect(vm.$el.querySelector('.js-build-variables')).toBeNull();
- });
- });
});
diff --git a/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js b/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
index 9092d3f8163..aeb85694e60 100644
--- a/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
+++ b/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UnmetPrerequisitesBlock from '~/jobs/components/unmet_prerequisites_block.vue';
describe('Unmet Prerequisites Block Job component', () => {
diff --git a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
index 2175610b7a6..838323df755 100644
--- a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
+++ b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
@@ -1,46 +1,42 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
describe('DelayedJobMixin', () => {
+ let wrapper;
const delayedJobFixture = getJSONFixture('jobs/delayed.json');
- const dummyComponent = Vue.extend({
- mixins: [delayedJobMixin],
+ const dummyComponent = {
props: {
job: {
type: Object,
required: true,
},
},
- render(createElement) {
- return createElement('div', this.remainingTime);
- },
- });
-
- let vm;
+ mixins: [delayedJobMixin],
+ template: '<div>{{remainingTime}}</div>',
+ };
afterEach(() => {
- vm.$destroy();
- jest.clearAllTimers();
+ wrapper.destroy();
+ wrapper = null;
});
describe('if job is empty object', () => {
beforeEach(() => {
- vm = mountComponent(dummyComponent, {
- job: {},
+ wrapper = shallowMount(dummyComponent, {
+ propsData: {
+ job: {},
+ },
});
});
it('sets remaining time to 00:00:00', () => {
- expect(vm.$el.innerText).toBe('00:00:00');
+ expect(wrapper.text()).toBe('00:00:00');
});
- describe('after mounting', () => {
- beforeEach(() => vm.$nextTick());
+ it('does not update remaining time after mounting', async () => {
+ await wrapper.vm.$nextTick();
- it('does not update remaining time', () => {
- expect(vm.$el.innerText).toBe('00:00:00');
- });
+ expect(wrapper.text()).toBe('00:00:00');
});
});
@@ -48,33 +44,32 @@ describe('DelayedJobMixin', () => {
describe('if job is delayed job', () => {
let remainingTimeInMilliseconds = 42000;
- beforeEach(() => {
+ beforeEach(async () => {
jest
.spyOn(Date, 'now')
.mockImplementation(
() => new Date(delayedJobFixture.scheduled_at).getTime() - remainingTimeInMilliseconds,
);
- vm = mountComponent(dummyComponent, {
- job: delayedJobFixture,
+ wrapper = shallowMount(dummyComponent, {
+ propsData: {
+ job: delayedJobFixture,
+ },
});
- });
- describe('after mounting', () => {
- beforeEach(() => vm.$nextTick());
+ await wrapper.vm.$nextTick();
+ });
- it('sets remaining time', () => {
- expect(vm.$el.innerText).toBe('00:00:42');
- });
+ it('sets remaining time', () => {
+ expect(wrapper.text()).toBe('00:00:42');
+ });
- it('updates remaining time', () => {
- remainingTimeInMilliseconds = 41000;
- jest.advanceTimersByTime(1000);
+ it('updates remaining time', async () => {
+ remainingTimeInMilliseconds = 41000;
+ jest.advanceTimersByTime(1000);
- return vm.$nextTick().then(() => {
- expect(vm.$el.innerText).toBe('00:00:41');
- });
- });
+ await wrapper.vm.$nextTick();
+ expect(wrapper.text()).toBe('00:00:41');
});
});
});
@@ -96,33 +91,32 @@ describe('DelayedJobMixin', () => {
describe('if job is delayed job', () => {
let remainingTimeInMilliseconds = 42000;
- beforeEach(() => {
+ beforeEach(async () => {
jest
.spyOn(Date, 'now')
.mockImplementation(
() => mockGraphQlJob.scheduledAt.getTime() - remainingTimeInMilliseconds,
);
- vm = mountComponent(dummyComponent, {
- job: mockGraphQlJob,
+ wrapper = shallowMount(dummyComponent, {
+ propsData: {
+ job: mockGraphQlJob,
+ },
});
- });
- describe('after mounting', () => {
- beforeEach(() => vm.$nextTick());
+ await wrapper.vm.$nextTick();
+ });
- it('sets remaining time', () => {
- expect(vm.$el.innerText).toBe('00:00:42');
- });
+ it('sets remaining time', () => {
+ expect(wrapper.text()).toBe('00:00:42');
+ });
- it('updates remaining time', () => {
- remainingTimeInMilliseconds = 41000;
- jest.advanceTimersByTime(1000);
+ it('updates remaining time', async () => {
+ remainingTimeInMilliseconds = 41000;
+ jest.advanceTimersByTime(1000);
- return vm.$nextTick().then(() => {
- expect(vm.$el.innerText).toBe('00:00:41');
- });
- });
+ await wrapper.vm.$nextTick();
+ expect(wrapper.text()).toBe('00:00:41');
});
});
});
diff --git a/spec/frontend/jobs/store/actions_spec.js b/spec/frontend/jobs/store/actions_spec.js
index 2d757ce76bf..a29bd15099f 100644
--- a/spec/frontend/jobs/store/actions_spec.js
+++ b/spec/frontend/jobs/store/actions_spec.js
@@ -1,7 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
+import testAction from 'helpers/vuex_action_helper';
import {
setJobEndpoint,
setTraceOptions,
@@ -28,8 +27,9 @@ import {
showSidebar,
toggleSidebar,
} from '~/jobs/store/actions';
-import state from '~/jobs/store/state';
import * as types from '~/jobs/store/mutation_types';
+import state from '~/jobs/store/state';
+import axios from '~/lib/utils/axios_utils';
describe('Job State actions', () => {
let mockedState;
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index 608abc8f7c4..1c7e45dfb3d 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/jobs/store/state';
-import mutations from '~/jobs/store/mutations';
import * as types from '~/jobs/store/mutation_types';
+import mutations from '~/jobs/store/mutations';
+import state from '~/jobs/store/state';
describe('Jobs Store Mutations', () => {
let stateCopy;
diff --git a/spec/frontend/lazy_loader_spec.js b/spec/frontend/lazy_loader_spec.js
index f8ac7568724..3d8b0d9c307 100644
--- a/spec/frontend/lazy_loader_spec.js
+++ b/spec/frontend/lazy_loader_spec.js
@@ -1,6 +1,6 @@
import { noop } from 'lodash';
-import { TEST_HOST } from 'helpers/test_constants';
import { useMockMutationObserver, useMockIntersectionObserver } from 'helpers/mock_dom_observer';
+import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import LazyLoader from '~/lazy_loader';
diff --git a/spec/frontend/lib/utils/ajax_cache_spec.js b/spec/frontend/lib/utils/ajax_cache_spec.js
index 641dd3684fa..d4b95172d18 100644
--- a/spec/frontend/lib/utils/ajax_cache_spec.js
+++ b/spec/frontend/lib/utils/ajax_cache_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import AjaxCache from '~/lib/utils/ajax_cache';
+import axios from '~/lib/utils/axios_utils';
describe('AjaxCache', () => {
const dummyEndpoint = '/AjaxCache/dummyEndpoint';
diff --git a/spec/frontend/lib/utils/array_utility_spec.js b/spec/frontend/lib/utils/array_utility_spec.js
new file mode 100644
index 00000000000..b95286ff254
--- /dev/null
+++ b/spec/frontend/lib/utils/array_utility_spec.js
@@ -0,0 +1,32 @@
+import * as arrayUtils from '~/lib/utils/array_utility';
+
+describe('array_utility', () => {
+ describe('swapArrayItems', () => {
+ it.each`
+ array | leftIndex | rightIndex | result
+ ${[]} | ${0} | ${0} | ${[]}
+ ${[1]} | ${0} | ${1} | ${[1]}
+ ${[1, 2]} | ${0} | ${0} | ${[1, 2]}
+ ${[1, 2]} | ${0} | ${1} | ${[2, 1]}
+ ${[1, 2]} | ${1} | ${2} | ${[1, 2]}
+ ${[1, 2]} | ${2} | ${1} | ${[1, 2]}
+ ${[1, 2]} | ${1} | ${10} | ${[1, 2]}
+ ${[1, 2]} | ${10} | ${1} | ${[1, 2]}
+ ${[1, 2]} | ${1} | ${-1} | ${[1, 2]}
+ ${[1, 2]} | ${-1} | ${1} | ${[1, 2]}
+ ${[1, 2, 3]} | ${1} | ${1} | ${[1, 2, 3]}
+ ${[1, 2, 3]} | ${0} | ${2} | ${[3, 2, 1]}
+ ${[1, 2, 3, 4]} | ${0} | ${2} | ${[3, 2, 1, 4]}
+ ${[1, 2, 3, 4, 5]} | ${0} | ${4} | ${[5, 2, 3, 4, 1]}
+ ${[1, 2, 3, 4, 5]} | ${1} | ${2} | ${[1, 3, 2, 4, 5]}
+ ${[1, 2, 3, 4, 5]} | ${2} | ${1} | ${[1, 3, 2, 4, 5]}
+ `(
+ 'given $array with index $leftIndex and $rightIndex will return $result',
+ ({ array, leftIndex, rightIndex, result }) => {
+ const actual = arrayUtils.swapArrayItems(array, leftIndex, rightIndex);
+ expect(actual).toEqual(result);
+ expect(actual).not.toBe(array);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/lib/utils/color_utils_spec.js b/spec/frontend/lib/utils/color_utils_spec.js
index 433e9d5a85e..8c846abd77f 100644
--- a/spec/frontend/lib/utils/color_utils_spec.js
+++ b/spec/frontend/lib/utils/color_utils_spec.js
@@ -1,4 +1,4 @@
-import { textColorForBackground, hexToRgb } from '~/lib/utils/color_utils';
+import { textColorForBackground, hexToRgb, validateHexColor } from '~/lib/utils/color_utils';
describe('Color utils', () => {
describe('Converting hex code to rgb', () => {
@@ -32,4 +32,19 @@ describe('Color utils', () => {
expect(textColorForBackground('#000')).toEqual('#FFFFFF');
});
});
+
+ describe('Validate hex color', () => {
+ it.each`
+ color | output
+ ${undefined} | ${null}
+ ${null} | ${null}
+ ${''} | ${null}
+ ${'ABC123'} | ${false}
+ ${'#ZZZ'} | ${false}
+ ${'#FF0'} | ${true}
+ ${'#FF0000'} | ${true}
+ `('returns $output when $color is given', ({ color, output }) => {
+ expect(validateHexColor(color)).toEqual(output);
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 90222f0f718..18be88a0b8b 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -1045,4 +1045,12 @@ describe('common_utils', () => {
expect(commonUtils.getDashPath('/some/url')).toEqual(null);
});
});
+
+ describe('convertArrayToCamelCase', () => {
+ it('returns a new array with snake_case string elements converted camelCase', () => {
+ const result = commonUtils.convertArrayToCamelCase(['hello', 'hello_world']);
+
+ expect(result).toEqual(['hello', 'helloWorld']);
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index 66efd43262b..32a24227cbd 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
import timezoneMock from 'timezone-mock';
+import * as datetimeUtility from '~/lib/utils/datetime_utility';
import { __, s__ } from '~/locale';
import '~/commons/bootstrap';
-import * as datetimeUtility from '~/lib/utils/datetime_utility';
describe('Date time utils', () => {
describe('timeFor', () => {
@@ -584,22 +584,6 @@ describe('secondsToMilliseconds', () => {
});
});
-describe('dayAfter', () => {
- const date = new Date('2019-07-16T00:00:00.000Z');
-
- it('returns the following date', () => {
- const nextDay = datetimeUtility.dayAfter(date);
- const expectedNextDate = new Date('2019-07-17T00:00:00.000Z');
-
- expect(nextDay).toStrictEqual(expectedNextDate);
- });
-
- it('does not modifiy the original date', () => {
- datetimeUtility.dayAfter(date);
- expect(date).toStrictEqual(new Date('2019-07-16T00:00:00.000Z'));
- });
-});
-
describe('secondsToDays', () => {
it('converts seconds to days correctly', () => {
expect(datetimeUtility.secondsToDays(0)).toBe(0);
@@ -608,90 +592,214 @@ describe('secondsToDays', () => {
});
});
-describe('nDaysAfter', () => {
- const date = new Date('2019-07-16T00:00:00.000Z');
+describe('date addition/subtraction methods', () => {
+ beforeEach(() => {
+ timezoneMock.register('US/Eastern');
+ });
- it.each`
- numberOfDays | expectedResult
- ${1} | ${new Date('2019-07-17T00:00:00.000Z').valueOf()}
- ${90} | ${new Date('2019-10-14T00:00:00.000Z').valueOf()}
- ${-1} | ${new Date('2019-07-15T00:00:00.000Z').valueOf()}
- ${0} | ${date.valueOf()}
- ${0.9} | ${date.valueOf()}
- `('returns $numberOfDays day(s) after the provided date', ({ numberOfDays, expectedResult }) => {
- expect(datetimeUtility.nDaysAfter(date, numberOfDays)).toBe(expectedResult);
+ afterEach(() => {
+ timezoneMock.unregister();
});
-});
-describe('nDaysBefore', () => {
- const date = new Date('2019-07-16T00:00:00.000Z');
+ describe('dayAfter', () => {
+ const input = '2019-03-10T00:00:00.000Z';
+ const expectedLocalResult = '2019-03-10T23:00:00.000Z';
+ const expectedUTCResult = '2019-03-11T00:00:00.000Z';
+
+ it.each`
+ inputAsString | options | expectedAsString
+ ${input} | ${undefined} | ${expectedLocalResult}
+ ${input} | ${{}} | ${expectedLocalResult}
+ ${input} | ${{ utc: false }} | ${expectedLocalResult}
+ ${input} | ${{ utc: true }} | ${expectedUTCResult}
+ `(
+ 'when the provided date is $inputAsString and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.dayAfter(inputDate, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
+
+ it('does not modifiy the original date', () => {
+ const inputDate = new Date(input);
+ datetimeUtility.dayAfter(inputDate);
+ expect(inputDate.toISOString()).toBe(input);
+ });
+ });
- it.each`
- numberOfDays | expectedResult
- ${1} | ${new Date('2019-07-15T00:00:00.000Z').valueOf()}
- ${90} | ${new Date('2019-04-17T00:00:00.000Z').valueOf()}
- ${-1} | ${new Date('2019-07-17T00:00:00.000Z').valueOf()}
- ${0} | ${date.valueOf()}
- ${0.9} | ${new Date('2019-07-15T00:00:00.000Z').valueOf()}
- `('returns $numberOfDays day(s) before the provided date', ({ numberOfDays, expectedResult }) => {
- expect(datetimeUtility.nDaysBefore(date, numberOfDays)).toBe(expectedResult);
+ describe('nDaysAfter', () => {
+ const input = '2019-07-16T00:00:00.000Z';
+
+ it.each`
+ inputAsString | numberOfDays | options | expectedAsString
+ ${input} | ${1} | ${undefined} | ${'2019-07-17T00:00:00.000Z'}
+ ${input} | ${-1} | ${undefined} | ${'2019-07-15T00:00:00.000Z'}
+ ${input} | ${0} | ${undefined} | ${'2019-07-16T00:00:00.000Z'}
+ ${input} | ${0.9} | ${undefined} | ${'2019-07-16T00:00:00.000Z'}
+ ${input} | ${120} | ${undefined} | ${'2019-11-13T01:00:00.000Z'}
+ ${input} | ${120} | ${{}} | ${'2019-11-13T01:00:00.000Z'}
+ ${input} | ${120} | ${{ utc: false }} | ${'2019-11-13T01:00:00.000Z'}
+ ${input} | ${120} | ${{ utc: true }} | ${'2019-11-13T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString, numberOfDays is $numberOfDays, and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, numberOfDays, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.nDaysAfter(inputDate, numberOfDays, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
});
-});
-describe('nMonthsAfter', () => {
- // February has 28 days
- const feb2019 = new Date('2019-02-15T00:00:00.000Z');
- // Except in 2020, it had 29 days
- const feb2020 = new Date('2020-02-15T00:00:00.000Z');
- // April has 30 days
- const apr2020 = new Date('2020-04-15T00:00:00.000Z');
- // May has 31 days
- const may2020 = new Date('2020-05-15T00:00:00.000Z');
+ describe('nDaysBefore', () => {
+ const input = '2019-07-16T00:00:00.000Z';
+
+ it.each`
+ inputAsString | numberOfDays | options | expectedAsString
+ ${input} | ${1} | ${undefined} | ${'2019-07-15T00:00:00.000Z'}
+ ${input} | ${-1} | ${undefined} | ${'2019-07-17T00:00:00.000Z'}
+ ${input} | ${0} | ${undefined} | ${'2019-07-16T00:00:00.000Z'}
+ ${input} | ${0.9} | ${undefined} | ${'2019-07-15T00:00:00.000Z'}
+ ${input} | ${180} | ${undefined} | ${'2019-01-17T01:00:00.000Z'}
+ ${input} | ${180} | ${{}} | ${'2019-01-17T01:00:00.000Z'}
+ ${input} | ${180} | ${{ utc: false }} | ${'2019-01-17T01:00:00.000Z'}
+ ${input} | ${180} | ${{ utc: true }} | ${'2019-01-17T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString, numberOfDays is $numberOfDays, and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, numberOfDays, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.nDaysBefore(inputDate, numberOfDays, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
+ });
- it.each`
- date | numberOfMonths | expectedResult
- ${feb2019} | ${1} | ${new Date('2019-03-15T00:00:00.000Z').valueOf()}
- ${feb2020} | ${1} | ${new Date('2020-03-15T00:00:00.000Z').valueOf()}
- ${apr2020} | ${1} | ${new Date('2020-05-15T00:00:00.000Z').valueOf()}
- ${may2020} | ${1} | ${new Date('2020-06-15T00:00:00.000Z').valueOf()}
- ${may2020} | ${12} | ${new Date('2021-05-15T00:00:00.000Z').valueOf()}
- ${may2020} | ${-1} | ${new Date('2020-04-15T00:00:00.000Z').valueOf()}
- ${may2020} | ${0} | ${may2020.valueOf()}
- ${may2020} | ${0.9} | ${may2020.valueOf()}
- `(
- 'returns $numberOfMonths month(s) after the provided date',
- ({ date, numberOfMonths, expectedResult }) => {
- expect(datetimeUtility.nMonthsAfter(date, numberOfMonths)).toBe(expectedResult);
- },
- );
-});
+ describe('nWeeksAfter', () => {
+ const input = '2021-07-16T00:00:00.000Z';
+
+ it.each`
+ inputAsString | numberOfWeeks | options | expectedAsString
+ ${input} | ${1} | ${undefined} | ${'2021-07-23T00:00:00.000Z'}
+ ${input} | ${3} | ${undefined} | ${'2021-08-06T00:00:00.000Z'}
+ ${input} | ${-1} | ${undefined} | ${'2021-07-09T00:00:00.000Z'}
+ ${input} | ${0} | ${undefined} | ${'2021-07-16T00:00:00.000Z'}
+ ${input} | ${0.6} | ${undefined} | ${'2021-07-20T00:00:00.000Z'}
+ ${input} | ${18} | ${undefined} | ${'2021-11-19T01:00:00.000Z'}
+ ${input} | ${18} | ${{}} | ${'2021-11-19T01:00:00.000Z'}
+ ${input} | ${18} | ${{ utc: false }} | ${'2021-11-19T01:00:00.000Z'}
+ ${input} | ${18} | ${{ utc: true }} | ${'2021-11-19T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString, numberOfWeeks is $numberOfWeeks, and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, numberOfWeeks, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.nWeeksAfter(inputDate, numberOfWeeks, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
+ });
-describe('nMonthsBefore', () => {
- // The previous month (February) has 28 days
- const march2019 = new Date('2019-03-15T00:00:00.000Z');
- // Except in 2020, it had 29 days
- const march2020 = new Date('2020-03-15T00:00:00.000Z');
- // The previous month (April) has 30 days
- const may2020 = new Date('2020-05-15T00:00:00.000Z');
- // The previous month (May) has 31 days
- const june2020 = new Date('2020-06-15T00:00:00.000Z');
+ describe('nWeeksBefore', () => {
+ const input = '2021-07-16T00:00:00.000Z';
+
+ it.each`
+ inputAsString | numberOfWeeks | options | expectedAsString
+ ${input} | ${1} | ${undefined} | ${'2021-07-09T00:00:00.000Z'}
+ ${input} | ${3} | ${undefined} | ${'2021-06-25T00:00:00.000Z'}
+ ${input} | ${-1} | ${undefined} | ${'2021-07-23T00:00:00.000Z'}
+ ${input} | ${0} | ${undefined} | ${'2021-07-16T00:00:00.000Z'}
+ ${input} | ${0.6} | ${undefined} | ${'2021-07-11T00:00:00.000Z'}
+ ${input} | ${20} | ${undefined} | ${'2021-02-26T01:00:00.000Z'}
+ ${input} | ${20} | ${{}} | ${'2021-02-26T01:00:00.000Z'}
+ ${input} | ${20} | ${{ utc: false }} | ${'2021-02-26T01:00:00.000Z'}
+ ${input} | ${20} | ${{ utc: true }} | ${'2021-02-26T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString, numberOfWeeks is $numberOfWeeks, and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, numberOfWeeks, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.nWeeksBefore(inputDate, numberOfWeeks, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
+ });
- it.each`
- date | numberOfMonths | expectedResult
- ${march2019} | ${1} | ${new Date('2019-02-15T00:00:00.000Z').valueOf()}
- ${march2020} | ${1} | ${new Date('2020-02-15T00:00:00.000Z').valueOf()}
- ${may2020} | ${1} | ${new Date('2020-04-15T00:00:00.000Z').valueOf()}
- ${june2020} | ${1} | ${new Date('2020-05-15T00:00:00.000Z').valueOf()}
- ${june2020} | ${12} | ${new Date('2019-06-15T00:00:00.000Z').valueOf()}
- ${june2020} | ${-1} | ${new Date('2020-07-15T00:00:00.000Z').valueOf()}
- ${june2020} | ${0} | ${june2020.valueOf()}
- ${june2020} | ${0.9} | ${new Date('2020-05-15T00:00:00.000Z').valueOf()}
- `(
- 'returns $numberOfMonths month(s) before the provided date',
- ({ date, numberOfMonths, expectedResult }) => {
- expect(datetimeUtility.nMonthsBefore(date, numberOfMonths)).toBe(expectedResult);
- },
- );
+ describe('nMonthsAfter', () => {
+ // February has 28 days
+ const feb2019 = '2019-02-15T00:00:00.000Z';
+ // Except in 2020, it had 29 days
+ const feb2020 = '2020-02-15T00:00:00.000Z';
+ // April has 30 days
+ const apr2020 = '2020-04-15T00:00:00.000Z';
+ // May has 31 days
+ const may2020 = '2020-05-15T00:00:00.000Z';
+ // November 1, 2020 was the day Daylight Saving Time ended in 2020 (in the US)
+ const oct2020 = '2020-10-15T00:00:00.000Z';
+
+ it.each`
+ inputAsString | numberOfMonths | options | expectedAsString
+ ${feb2019} | ${1} | ${undefined} | ${'2019-03-14T23:00:00.000Z'}
+ ${feb2020} | ${1} | ${undefined} | ${'2020-03-14T23:00:00.000Z'}
+ ${apr2020} | ${1} | ${undefined} | ${'2020-05-15T00:00:00.000Z'}
+ ${may2020} | ${1} | ${undefined} | ${'2020-06-15T00:00:00.000Z'}
+ ${may2020} | ${12} | ${undefined} | ${'2021-05-15T00:00:00.000Z'}
+ ${may2020} | ${-1} | ${undefined} | ${'2020-04-15T00:00:00.000Z'}
+ ${may2020} | ${0} | ${undefined} | ${may2020}
+ ${may2020} | ${0.9} | ${undefined} | ${may2020}
+ ${oct2020} | ${1} | ${undefined} | ${'2020-11-15T01:00:00.000Z'}
+ ${oct2020} | ${1} | ${{}} | ${'2020-11-15T01:00:00.000Z'}
+ ${oct2020} | ${1} | ${{ utc: false }} | ${'2020-11-15T01:00:00.000Z'}
+ ${oct2020} | ${1} | ${{ utc: true }} | ${'2020-11-15T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString, numberOfMonths is $numberOfMonths, and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, numberOfMonths, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.nMonthsAfter(inputDate, numberOfMonths, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
+ });
+
+ describe('nMonthsBefore', () => {
+ // The previous month (February) has 28 days
+ const march2019 = '2019-03-15T00:00:00.000Z';
+ // Except in 2020, it had 29 days
+ const march2020 = '2020-03-15T00:00:00.000Z';
+ // The previous month (April) has 30 days
+ const may2020 = '2020-05-15T00:00:00.000Z';
+ // The previous month (May) has 31 days
+ const june2020 = '2020-06-15T00:00:00.000Z';
+ // November 1, 2020 was the day Daylight Saving Time ended in 2020 (in the US)
+ const nov2020 = '2020-11-15T00:00:00.000Z';
+
+ it.each`
+ inputAsString | numberOfMonths | options | expectedAsString
+ ${march2019} | ${1} | ${undefined} | ${'2019-02-15T01:00:00.000Z'}
+ ${march2020} | ${1} | ${undefined} | ${'2020-02-15T01:00:00.000Z'}
+ ${may2020} | ${1} | ${undefined} | ${'2020-04-15T00:00:00.000Z'}
+ ${june2020} | ${1} | ${undefined} | ${'2020-05-15T00:00:00.000Z'}
+ ${june2020} | ${12} | ${undefined} | ${'2019-06-15T00:00:00.000Z'}
+ ${june2020} | ${-1} | ${undefined} | ${'2020-07-15T00:00:00.000Z'}
+ ${june2020} | ${0} | ${undefined} | ${june2020}
+ ${june2020} | ${0.9} | ${undefined} | ${'2020-05-15T00:00:00.000Z'}
+ ${nov2020} | ${1} | ${undefined} | ${'2020-10-14T23:00:00.000Z'}
+ ${nov2020} | ${1} | ${{}} | ${'2020-10-14T23:00:00.000Z'}
+ ${nov2020} | ${1} | ${{ utc: false }} | ${'2020-10-14T23:00:00.000Z'}
+ ${nov2020} | ${1} | ${{ utc: true }} | ${'2020-10-15T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString, numberOfMonths is $numberOfMonths, and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, numberOfMonths, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.nMonthsBefore(inputDate, numberOfMonths, options);
+
+ expect(actual.toISOString()).toBe(expectedAsString);
+ },
+ );
+ });
});
describe('approximateDuration', () => {
@@ -843,7 +951,7 @@ describe('format24HourTimeStringFromInt', () => {
});
});
-describe('getOverlappingDaysInPeriods', () => {
+describe('getOverlapDateInPeriods', () => {
const start = new Date(2021, 0, 11);
const end = new Date(2021, 0, 13);
@@ -851,14 +959,15 @@ describe('getOverlappingDaysInPeriods', () => {
const givenPeriodLeft = new Date(2021, 0, 11);
const givenPeriodRight = new Date(2021, 0, 14);
- it('returns an overlap object that contains the amount of days overlapping, start date of overlap and end date of overlap', () => {
+ it('returns an overlap object that contains the amount of days overlapping, the amount of hours overlapping, start date of overlap and end date of overlap', () => {
expect(
- datetimeUtility.getOverlappingDaysInPeriods(
+ datetimeUtility.getOverlapDateInPeriods(
{ start, end },
{ start: givenPeriodLeft, end: givenPeriodRight },
),
).toEqual({
daysOverlap: 2,
+ hoursOverlap: 48,
overlapStartDate: givenPeriodLeft.getTime(),
overlapEndDate: end.getTime(),
});
@@ -871,7 +980,7 @@ describe('getOverlappingDaysInPeriods', () => {
it('returns an overlap object that contains a 0 value for days overlapping', () => {
expect(
- datetimeUtility.getOverlappingDaysInPeriods(
+ datetimeUtility.getOverlapDateInPeriods(
{ start, end },
{ start: givenPeriodLeft, end: givenPeriodRight },
),
@@ -886,14 +995,54 @@ describe('getOverlappingDaysInPeriods', () => {
it('throws an exception when the left period contains an invalid date', () => {
expect(() =>
- datetimeUtility.getOverlappingDaysInPeriods({ start, end }, { start: startInvalid, end }),
+ datetimeUtility.getOverlapDateInPeriods({ start, end }, { start: startInvalid, end }),
).toThrow(error);
});
it('throws an exception when the right period contains an invalid date', () => {
expect(() =>
- datetimeUtility.getOverlappingDaysInPeriods({ start, end }, { start, end: endInvalid }),
+ datetimeUtility.getOverlapDateInPeriods({ start, end }, { start, end: endInvalid }),
).toThrow(error);
});
});
});
+
+describe('isToday', () => {
+ const today = new Date();
+ it.each`
+ date | expected | negation
+ ${today} | ${true} | ${'is'}
+ ${new Date('2021-01-21T12:00:00.000Z')} | ${false} | ${'is NOT'}
+ `('returns $expected as $date $negation today', ({ date, expected }) => {
+ expect(datetimeUtility.isToday(date)).toBe(expected);
+ });
+});
+
+describe('getStartOfDay', () => {
+ beforeEach(() => {
+ timezoneMock.register('US/Eastern');
+ });
+
+ afterEach(() => {
+ timezoneMock.unregister();
+ });
+
+ it.each`
+ inputAsString | options | expectedAsString
+ ${'2021-01-29T18:08:23.014Z'} | ${undefined} | ${'2021-01-29T05:00:00.000Z'}
+ ${'2021-01-29T13:08:23.014-05:00'} | ${undefined} | ${'2021-01-29T05:00:00.000Z'}
+ ${'2021-01-30T03:08:23.014+09:00'} | ${undefined} | ${'2021-01-29T05:00:00.000Z'}
+ ${'2021-01-28T18:08:23.014-10:00'} | ${undefined} | ${'2021-01-28T05:00:00.000Z'}
+ ${'2021-01-28T18:08:23.014-10:00'} | ${{}} | ${'2021-01-28T05:00:00.000Z'}
+ ${'2021-01-28T18:08:23.014-10:00'} | ${{ utc: false }} | ${'2021-01-28T05:00:00.000Z'}
+ ${'2021-01-28T18:08:23.014-10:00'} | ${{ utc: true }} | ${'2021-01-29T00:00:00.000Z'}
+ `(
+ 'when the provided date is $inputAsString and the options parameter is $options, returns $expectedAsString',
+ ({ inputAsString, options, expectedAsString }) => {
+ const inputDate = new Date(inputAsString);
+ const actual = datetimeUtility.getStartOfDay(inputDate, options);
+
+ expect(actual.toISOString()).toEqual(expectedAsString);
+ },
+ );
+});
diff --git a/spec/frontend/lib/utils/poll_spec.js b/spec/frontend/lib/utils/poll_spec.js
index f2ca5df3672..861808e3ad8 100644
--- a/spec/frontend/lib/utils/poll_spec.js
+++ b/spec/frontend/lib/utils/poll_spec.js
@@ -1,6 +1,6 @@
import waitForPromises from 'helpers/wait_for_promises';
-import Poll from '~/lib/utils/poll';
import { successCodes } from '~/lib/utils/http_status';
+import Poll from '~/lib/utils/poll';
describe('Poll', () => {
let callbacks;
diff --git a/spec/frontend/lib/utils/poll_until_complete_spec.js b/spec/frontend/lib/utils/poll_until_complete_spec.js
index 38203c460e3..7509f954a84 100644
--- a/spec/frontend/lib/utils/poll_until_complete_spec.js
+++ b/spec/frontend/lib/utils/poll_until_complete_spec.js
@@ -1,8 +1,8 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import pollUntilComplete from '~/lib/utils/poll_until_complete';
import httpStatusCodes from '~/lib/utils/http_status';
+import pollUntilComplete from '~/lib/utils/poll_until_complete';
const endpoint = `${TEST_HOST}/foo`;
const mockData = 'mockData';
diff --git a/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js b/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js
index 26b942c3567..0ca70e0a77e 100644
--- a/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js
+++ b/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js
@@ -36,6 +36,27 @@ describe('unit_format/formatter_factory', () => {
expect(formatNumber(10 ** 7, undefined, 9)).toBe('1.00e+7');
expect(formatNumber(10 ** 7, undefined, 10)).toBe('10,000,000');
});
+
+ describe('formats with a different locale', () => {
+ let originalLang;
+
+ beforeAll(() => {
+ originalLang = document.documentElement.lang;
+ document.documentElement.lang = 'es';
+ });
+
+ afterAll(() => {
+ document.documentElement.lang = originalLang;
+ });
+
+ it('formats a using the correct thousands separator', () => {
+ expect(formatNumber(1000000)).toBe('1.000.000');
+ });
+
+ it('formats a using the correct decimal separator', () => {
+ expect(formatNumber(12.345)).toBe('12,345');
+ });
+ });
});
describe('suffixFormatter', () => {
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 5846acbdb79..b60ddea81ee 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -492,6 +492,28 @@ describe('URL utility', () => {
});
});
+ describe('isExternal', () => {
+ const gitlabUrl = 'https://gitlab.com/';
+
+ beforeEach(() => {
+ gon.gitlab_url = gitlabUrl;
+ });
+
+ afterEach(() => {
+ gon.gitlab_url = '';
+ });
+
+ it.each`
+ url | urlType | external
+ ${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false}
+ ${gitlabUrl} | ${'absolute and internal'} | ${false}
+ ${`${gitlabUrl}/gitlab-org/gitlab-test`} | ${'absolute and internal'} | ${false}
+ ${'http://jira.atlassian.net/browse/IG-1'} | ${'absolute and external'} | ${true}
+ `('returns $external for $url ($urlType)', ({ url, external }) => {
+ expect(urlUtils.isExternal(url)).toBe(external);
+ });
+ });
+
describe('isBase64DataUrl', () => {
it.each`
url | valid
@@ -858,4 +880,37 @@ describe('URL utility', () => {
expect(urlUtils.getURLOrigin(url)).toBe(expectation);
});
});
+
+ describe('encodeSaferUrl', () => {
+ it.each`
+ character | input | output
+ ${' '} | ${'/url/hello 1.jpg'} | ${'/url/hello%201.jpg'}
+ ${'#'} | ${'/url/hello#1.jpg'} | ${'/url/hello%231.jpg'}
+ ${'!'} | ${'/url/hello!.jpg'} | ${'/url/hello%21.jpg'}
+ ${'~'} | ${'/url/hello~.jpg'} | ${'/url/hello%7E.jpg'}
+ ${'*'} | ${'/url/hello*.jpg'} | ${'/url/hello%2A.jpg'}
+ ${"'"} | ${"/url/hello'.jpg"} | ${'/url/hello%27.jpg'}
+ ${'('} | ${'/url/hello(.jpg'} | ${'/url/hello%28.jpg'}
+ ${')'} | ${'/url/hello).jpg'} | ${'/url/hello%29.jpg'}
+ ${'?'} | ${'/url/hello?.jpg'} | ${'/url/hello%3F.jpg'}
+ ${'='} | ${'/url/hello=.jpg'} | ${'/url/hello%3D.jpg'}
+ ${'+'} | ${'/url/hello+.jpg'} | ${'/url/hello%2B.jpg'}
+ ${'&'} | ${'/url/hello&.jpg'} | ${'/url/hello%26.jpg'}
+ `(
+ 'properly escapes `$character` characters while retaining the integrity of the URL',
+ ({ input, output }) => {
+ expect(urlUtils.encodeSaferUrl(input)).toBe(output);
+ },
+ );
+
+ it.each`
+ character | input
+ ${'/, .'} | ${'/url/hello.png'}
+ ${'\\d'} | ${'/url/hello123.png'}
+ ${'-'} | ${'/url/hello-123.png'}
+ ${'_'} | ${'/url/hello_123.png'}
+ `('makes no changes to unproblematic characters ($character)', ({ input }) => {
+ expect(urlUtils.encodeSaferUrl(input)).toBe(input);
+ });
+ });
});
diff --git a/spec/frontend/line_highlighter_spec.js b/spec/frontend/line_highlighter_spec.js
index 1091bbf8aba..8318f63ab3e 100644
--- a/spec/frontend/line_highlighter_spec.js
+++ b/spec/frontend/line_highlighter_spec.js
@@ -1,8 +1,8 @@
/* eslint-disable no-return-assign, no-new, no-underscore-dangle */
import $ from 'jquery';
-import LineHighlighter from '~/line_highlighter';
import * as utils from '~/lib/utils/common_utils';
+import LineHighlighter from '~/line_highlighter';
describe('LineHighlighter', () => {
const testContext = {};
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
index 351ff317feb..b40d9d7d5e2 100644
--- a/spec/frontend/logs/components/environment_logs_spec.js
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -1,9 +1,9 @@
import { GlSprintf, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { scrollDown } from '~/lib/utils/scroll_utils';
import EnvironmentLogs from '~/logs/components/environment_logs.vue';
import { createStore } from '~/logs/stores';
-import { scrollDown } from '~/lib/utils/scroll_utils';
import {
mockEnvName,
mockEnvironments,
diff --git a/spec/frontend/logs/components/log_advanced_filters_spec.js b/spec/frontend/logs/components/log_advanced_filters_spec.js
index dfa8913a301..111542ff33e 100644
--- a/spec/frontend/logs/components/log_advanced_filters_spec.js
+++ b/spec/frontend/logs/components/log_advanced_filters_spec.js
@@ -1,13 +1,12 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFilteredSearch } from '@gitlab/ui';
-import { defaultTimeRange } from '~/vue_shared/constants';
+import { shallowMount } from '@vue/test-utils';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
-import { createStore } from '~/logs/stores';
+import LogAdvancedFilters from '~/logs/components/log_advanced_filters.vue';
import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
+import { createStore } from '~/logs/stores';
+import { defaultTimeRange } from '~/vue_shared/constants';
import { mockPods, mockSearch } from '../mock_data';
-import LogAdvancedFilters from '~/logs/components/log_advanced_filters.vue';
-
const module = 'environmentLogs';
describe('LogAdvancedFilters', () => {
diff --git a/spec/frontend/logs/components/log_control_buttons_spec.js b/spec/frontend/logs/components/log_control_buttons_spec.js
index 55b28445786..9c1617e4daa 100644
--- a/spec/frontend/logs/components/log_control_buttons_spec.js
+++ b/spec/frontend/logs/components/log_control_buttons_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import LogControlButtons from '~/logs/components/log_control_buttons.vue';
describe('LogControlButtons', () => {
diff --git a/spec/frontend/logs/components/log_simple_filters_spec.js b/spec/frontend/logs/components/log_simple_filters_spec.js
index 5bd42fd7dbc..04ad2e03542 100644
--- a/spec/frontend/logs/components/log_simple_filters_spec.js
+++ b/spec/frontend/logs/components/log_simple_filters_spec.js
@@ -1,10 +1,9 @@
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import LogSimpleFilters from '~/logs/components/log_simple_filters.vue';
import { createStore } from '~/logs/stores';
import { mockPods, mockPodName } from '../mock_data';
-import LogSimpleFilters from '~/logs/components/log_simple_filters.vue';
-
const module = 'environmentLogs';
describe('LogSimpleFilters', () => {
diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js
index bc58f1e677f..92c2f82af27 100644
--- a/spec/frontend/logs/stores/actions_spec.js
+++ b/spec/frontend/logs/stores/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import Tracking from '~/tracking';
-import * as types from '~/logs/stores/mutation_types';
+import { deprecatedCreateFlash as flash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
-import logsPageState from '~/logs/stores/state';
+import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
import {
setInitData,
showFilteredLogs,
@@ -13,12 +13,12 @@ import {
fetchMoreLogsPrepend,
fetchManagedApps,
} from '~/logs/stores/actions';
+import * as types from '~/logs/stores/mutation_types';
+import logsPageState from '~/logs/stores/state';
+import Tracking from '~/tracking';
import { defaultTimeRange } from '~/vue_shared/constants';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as flash } from '~/flash';
-
import {
mockPodName,
mockEnvironmentsEndpoint,
@@ -34,7 +34,6 @@ import {
mockManagedApps,
mockManagedAppsEndpoint,
} from '../mock_data';
-import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
jest.mock('~/flash');
jest.mock('~/lib/utils/datetime_range');
diff --git a/spec/frontend/logs/stores/mutations_spec.js b/spec/frontend/logs/stores/mutations_spec.js
index 51f6494b011..111c795ba52 100644
--- a/spec/frontend/logs/stores/mutations_spec.js
+++ b/spec/frontend/logs/stores/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/logs/stores/mutations';
import * as types from '~/logs/stores/mutation_types';
+import mutations from '~/logs/stores/mutations';
import logsPageState from '~/logs/stores/state';
import {
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 30166e2d5ae..f86237dc160 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
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue';
-import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue';
+import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
import { accessRequest as member } from '../../mock_data';
describe('AccessRequestActionButtons', () => {
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 7ce2c633bb3..f77d41a642e 100644
--- a/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
@@ -1,6 +1,6 @@
+import { GlButton, GlForm } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlButton, GlForm } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue';
diff --git a/spec/frontend/members/components/action_buttons/leave_button_spec.js b/spec/frontend/members/components/action_buttons/leave_button_spec.js
index 2afe112c74b..4859d033464 100644
--- a/spec/frontend/members/components/action_buttons/leave_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/leave_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import LeaveButton from '~/members/components/action_buttons/leave_button.vue';
import LeaveModal from '~/members/components/modals/leave_modal.vue';
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 45283788676..f6e342898cb 100644
--- a/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
@@ -1,6 +1,6 @@
+import { GlButton } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlButton } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import RemoveGroupLinkButton from '~/members/components/action_buttons/remove_group_link_button.vue';
import { group } from '../../mock_data';
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 05ea0dc2886..49b6979f954 100644
--- a/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
@@ -1,6 +1,6 @@
+import { GlButton } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlButton } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ResendInviteButton from '~/members/components/action_buttons/resend_invite_button.vue';
diff --git a/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
index f28e5040006..1d7ea5b3109 100644
--- a/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
-import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
import LeaveButton from '~/members/components/action_buttons/leave_button.vue';
+import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
+import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
import { member, orphanedMember } from '../../mock_data';
describe('UserActionButtons', () => {
diff --git a/spec/frontend/groups/members/components/app_spec.js b/spec/frontend/members/components/app_spec.js
index 9847dacbec8..a1329c3ee9f 100644
--- a/spec/frontend/groups/members/components/app_spec.js
+++ b/spec/frontend/members/components/app_spec.js
@@ -1,14 +1,14 @@
+import { GlAlert } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
-import { GlAlert } from '@gitlab/ui';
-import App from '~/groups/members/components/app.vue';
-import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import * as commonUtils from '~/lib/utils/common_utils';
+import MembersApp from '~/members/components/app.vue';
+import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_types';
import mutations from '~/members/store/mutations';
-describe('GroupMembersApp', () => {
+describe('MembersApp', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -25,7 +25,7 @@ describe('GroupMembersApp', () => {
mutations,
});
- wrapper = shallowMount(App, {
+ wrapper = shallowMount(MembersApp, {
localVue,
store,
...options,
@@ -48,7 +48,7 @@ describe('GroupMembersApp', () => {
it('renders and scrolls to error alert', async () => {
createComponent({ showError: false, errorMessage: '' });
- store.commit(RECEIVE_MEMBER_ROLE_ERROR);
+ store.commit(RECEIVE_MEMBER_ROLE_ERROR, { error: new Error('Network Error') });
await nextTick();
diff --git a/spec/frontend/members/components/avatars/group_avatar_spec.js b/spec/frontend/members/components/avatars/group_avatar_spec.js
index 658bb9462b0..9c1574a84ee 100644
--- a/spec/frontend/members/components/avatars/group_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/group_avatar_spec.js
@@ -1,8 +1,8 @@
-import { mount, createWrapper } from '@vue/test-utils';
-import { getByText as getByTextHelper } from '@testing-library/dom';
import { GlAvatarLink } from '@gitlab/ui';
-import { group as member } from '../../mock_data';
+import { getByText as getByTextHelper } from '@testing-library/dom';
+import { mount, createWrapper } from '@vue/test-utils';
import GroupAvatar from '~/members/components/avatars/group_avatar.vue';
+import { group as member } from '../../mock_data';
describe('MemberList', () => {
let wrapper;
diff --git a/spec/frontend/members/components/avatars/invite_avatar_spec.js b/spec/frontend/members/components/avatars/invite_avatar_spec.js
index 13ee727528b..b197a46c0d1 100644
--- a/spec/frontend/members/components/avatars/invite_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/invite_avatar_spec.js
@@ -1,7 +1,7 @@
-import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
-import { invite as member } from '../../mock_data';
+import { mount, createWrapper } from '@vue/test-utils';
import InviteAvatar from '~/members/components/avatars/invite_avatar.vue';
+import { invite as member } from '../../mock_data';
describe('MemberList', () => {
let wrapper;
diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js
index 411ec1a54de..303c82582a3 100644
--- a/spec/frontend/members/components/avatars/user_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/user_avatar_spec.js
@@ -1,8 +1,8 @@
-import { mount, createWrapper } from '@vue/test-utils';
-import { within } from '@testing-library/dom';
import { GlAvatarLink, GlBadge } from '@gitlab/ui';
-import { member as memberMock, orphanedMember } from '../../mock_data';
+import { within } from '@testing-library/dom';
+import { mount, createWrapper } from '@vue/test-utils';
import UserAvatar from '~/members/components/avatars/user_avatar.vue';
+import { member as memberMock, orphanedMember } from '../../mock_data';
describe('UserAvatar', () => {
let wrapper;
diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
index 2bed1e803ca..14b437a8c4e 100644
--- a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
+++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
@@ -1,6 +1,6 @@
+import { GlFilteredSearchToken } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlFilteredSearchToken } from '@gitlab/ui';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
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 d98c9116512..357fad741e9 100644
--- a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
+++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
@@ -1,8 +1,8 @@
+import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import * as urlUtilities from '~/lib/utils/url_utility';
+import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/members/components/modals/leave_modal_spec.js b/spec/frontend/members/components/modals/leave_modal_spec.js
index dca47d1f6af..2d52911572f 100644
--- a/spec/frontend/members/components/modals/leave_modal_spec.js
+++ b/spec/frontend/members/components/modals/leave_modal_spec.js
@@ -1,7 +1,7 @@
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import { GlModal, GlForm } from '@gitlab/ui';
-import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
+import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Vuex from 'vuex';
import LeaveModal from '~/members/components/modals/leave_modal.vue';
import { LEAVE_MODAL_ID } from '~/members/constants';
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 234857419b6..62df912c1a2 100644
--- a/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
+++ b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
@@ -1,7 +1,7 @@
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import { GlModal, GlForm } from '@gitlab/ui';
-import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
+import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Vuex from 'vuex';
import RemoveGroupLinkModal from '~/members/components/modals/remove_group_link_modal.vue';
import { REMOVE_GROUP_LINK_MODAL_ID } from '~/members/constants';
diff --git a/spec/frontend/members/components/table/created_at_spec.js b/spec/frontend/members/components/table/created_at_spec.js
index dc1f62722ab..74b71e22893 100644
--- a/spec/frontend/members/components/table/created_at_spec.js
+++ b/spec/frontend/members/components/table/created_at_spec.js
@@ -1,5 +1,5 @@
-import { mount, createWrapper } from '@vue/test-utils';
import { within } from '@testing-library/dom';
+import { mount, createWrapper } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
import CreatedAt from '~/members/components/table/created_at.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
diff --git a/spec/frontend/members/components/table/expiration_datepicker_spec.js b/spec/frontend/members/components/table/expiration_datepicker_spec.js
index 0caaafb8d7b..d26172b4ed1 100644
--- a/spec/frontend/members/components/table/expiration_datepicker_spec.js
+++ b/spec/frontend/members/components/table/expiration_datepicker_spec.js
@@ -1,7 +1,7 @@
+import { GlDatepicker } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
import { nextTick } from 'vue';
-import { GlDatepicker } from '@gitlab/ui';
+import Vuex from 'vuex';
import { useFakeDate } from 'helpers/fake_date';
import waitForPromises from 'helpers/wait_for_promises';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
diff --git a/spec/frontend/members/components/table/expires_at_spec.js b/spec/frontend/members/components/table/expires_at_spec.js
index 321008727cd..02fe3c6d684 100644
--- a/spec/frontend/members/components/table/expires_at_spec.js
+++ b/spec/frontend/members/components/table/expires_at_spec.js
@@ -1,5 +1,5 @@
-import { mount, createWrapper } from '@vue/test-utils';
import { within } from '@testing-library/dom';
+import { mount, createWrapper } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ExpiresAt from '~/members/components/table/expires_at.vue';
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 b7a6df3d054..546d09732d6 100644
--- a/spec/frontend/members/components/table/member_action_buttons_spec.js
+++ b/spec/frontend/members/components/table/member_action_buttons_spec.js
@@ -1,11 +1,11 @@
import { shallowMount } from '@vue/test-utils';
-import { MEMBER_TYPES } from '~/members/constants';
-import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
-import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
-import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
+import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue';
import GroupActionButtons from '~/members/components/action_buttons/group_action_buttons.vue';
import InviteActionButtons from '~/members/components/action_buttons/invite_action_buttons.vue';
-import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue';
+import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
+import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
describe('MemberActionButtons', () => {
let wrapper;
diff --git a/spec/frontend/members/components/table/member_avatar_spec.js b/spec/frontend/members/components/table/member_avatar_spec.js
index 4341dfbbaf9..3cce64effbc 100644
--- a/spec/frontend/members/components/table/member_avatar_spec.js
+++ b/spec/frontend/members/components/table/member_avatar_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
-import { MEMBER_TYPES } from '~/members/constants';
-import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
-import MemberAvatar from '~/members/components/table/member_avatar.vue';
-import UserAvatar from '~/members/components/avatars/user_avatar.vue';
import GroupAvatar from '~/members/components/avatars/group_avatar.vue';
import InviteAvatar from '~/members/components/avatars/invite_avatar.vue';
+import UserAvatar from '~/members/components/avatars/user_avatar.vue';
+import MemberAvatar from '~/members/components/table/member_avatar.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
describe('MemberList', () => {
let wrapper;
diff --git a/spec/frontend/members/components/table/member_source_spec.js b/spec/frontend/members/components/table/member_source_spec.js
index 95547090aed..2cd888207b1 100644
--- a/spec/frontend/members/components/table/member_source_spec.js
+++ b/spec/frontend/members/components/table/member_source_spec.js
@@ -1,5 +1,5 @@
-import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
+import { mount, createWrapper } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MemberSource from '~/members/components/table/member_source.vue';
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 117c9255c00..b7dcd2a9fae 100644
--- a/spec/frontend/members/components/table/members_table_cell_spec.js
+++ b/spec/frontend/members/components/table/members_table_cell_spec.js
@@ -1,8 +1,15 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { MEMBER_TYPES } from '~/members/constants';
-import { member as memberMock, group, invite, accessRequest } from '../../mock_data';
import MembersTableCell from '~/members/components/table/members_table_cell.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import {
+ member as memberMock,
+ directMember,
+ inheritedMember,
+ group,
+ invite,
+ accessRequest,
+} from '../../mock_data';
describe('MembersTableCell', () => {
const WrappedComponent = {
@@ -31,7 +38,7 @@ describe('MembersTableCell', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
- localVue.component('wrapped-component', WrappedComponent);
+ localVue.component('WrappedComponent', WrappedComponent);
const createStore = (state = {}) => {
return new Vuex.Store({
@@ -75,19 +82,12 @@ describe('MembersTableCell', () => {
const createComponentWithDirectMember = (member = {}) => {
createComponent({
- member: {
- ...memberMock,
- source: {
- ...memberMock.source,
- id: 1,
- },
- ...member,
- },
+ member: { ...directMember, ...member },
});
};
const createComponentWithInheritedMember = (member = {}) => {
createComponent({
- member: { ...memberMock, ...member },
+ member: { ...inheritedMember, ...member },
});
};
diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index dbaccde069c..cf5811e72e7 100644
--- a/spec/frontend/members/components/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -1,21 +1,21 @@
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
-import Vuex from 'vuex';
+import { GlBadge, GlTable } from '@gitlab/ui';
import {
getByText as getByTextHelper,
getByTestId as getByTestIdHelper,
within,
} from '@testing-library/dom';
-import { GlBadge, GlTable } from '@gitlab/ui';
-import MembersTable from '~/members/components/table/members_table.vue';
-import MemberAvatar from '~/members/components/table/member_avatar.vue';
-import MemberSource from '~/members/components/table/member_source.vue';
-import ExpiresAt from '~/members/components/table/expires_at.vue';
+import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
+import Vuex from 'vuex';
import CreatedAt from '~/members/components/table/created_at.vue';
-import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
+import ExpiresAt from '~/members/components/table/expires_at.vue';
import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
+import MemberAvatar from '~/members/components/table/member_avatar.vue';
+import MemberSource from '~/members/components/table/member_source.vue';
+import MembersTable from '~/members/components/table/members_table.vue';
+import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import * as initUserPopovers from '~/user_popovers';
-import { member as memberMock, invite, accessRequest } from '../../mock_data';
+import { member as memberMock, directMember, invite, accessRequest } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -74,11 +74,6 @@ describe('MembersTable', () => {
});
describe('fields', () => {
- const directMember = {
- ...memberMock,
- source: { ...memberMock.source, id: 1 },
- };
-
const memberCanUpdate = {
...directMember,
canUpdate: true,
@@ -154,7 +149,7 @@ describe('MembersTable', () => {
expect(findTableCellByMemberId('Actions', members[0].id).classes()).toStrictEqual([
'col-actions',
'gl-display-none!',
- 'gl-display-lg-table-cell!',
+ 'gl-lg-display-table-cell!',
]);
expect(findTableCellByMemberId('Actions', members[1].id).classes()).toStrictEqual([
'col-actions',
diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js
index 96a388614f3..aa280599061 100644
--- a/spec/frontend/members/components/table/role_dropdown_spec.js
+++ b/spec/frontend/members/components/table/role_dropdown_spec.js
@@ -1,10 +1,11 @@
-import { mount, createWrapper, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import { nextTick } from 'vue';
-import { within } from '@testing-library/dom';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { within } from '@testing-library/dom';
+import { mount, createWrapper, createLocalVue } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
+import { BV_DROPDOWN_SHOW } from '~/lib/utils/constants';
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import { member } from '../../mock_data';
@@ -67,7 +68,7 @@ describe('RoleDropdown', () => {
createComponent();
findDropdownToggle().trigger('click');
- wrapper.vm.$root.$on('bv::dropdown::shown', () => {
+ wrapper.vm.$root.$on(BV_DROPDOWN_SHOW, () => {
done();
});
});
diff --git a/spec/frontend/groups/members/index_spec.js b/spec/frontend/members/index_spec.js
index 5c717e53229..dd3b9ddd912 100644
--- a/spec/frontend/groups/members/index_spec.js
+++ b/spec/frontend/members/index_spec.js
@@ -1,15 +1,15 @@
import { createWrapper } from '@vue/test-utils';
-import { initGroupMembersApp } from '~/groups/members';
-import GroupMembersApp from '~/groups/members/components/app.vue';
-import { membersJsonString, membersParsed } from './mock_data';
+import MembersApp from '~/members/components/app.vue';
+import { initMembersApp } from '~/members/index';
+import { membersJsonString, members } from './mock_data';
-describe('initGroupMembersApp', () => {
+describe('initMembersApp', () => {
let el;
let vm;
let wrapper;
const setup = () => {
- vm = initGroupMembersApp(el, {
+ vm = initMembersApp(el, {
tableFields: ['account'],
tableAttrs: { table: { 'data-qa-selector': 'members_list' } },
tableSortableFields: ['account'],
@@ -22,7 +22,7 @@ describe('initGroupMembersApp', () => {
beforeEach(() => {
el = document.createElement('div');
el.setAttribute('data-members', membersJsonString);
- el.setAttribute('data-group-id', '234');
+ el.setAttribute('data-source-id', '234');
el.setAttribute('data-can-manage-members', 'true');
el.setAttribute('data-member-path', '/groups/foo-bar/-/group_members/:id');
@@ -36,10 +36,10 @@ describe('initGroupMembersApp', () => {
wrapper = null;
});
- it('renders `GroupMembersApp`', () => {
+ it('renders `MembersApp`', () => {
setup();
- expect(wrapper.find(GroupMembersApp).exists()).toBe(true);
+ expect(wrapper.find(MembersApp).exists()).toBe(true);
});
it('sets `currentUserId` in Vuex store', () => {
@@ -57,7 +57,7 @@ describe('initGroupMembersApp', () => {
});
});
- it('parses and sets `data-group-id` as `sourceId` in Vuex store', () => {
+ it('parses and sets `data-source-id` as `sourceId` in Vuex store', () => {
setup();
expect(vm.$store.state.sourceId).toBe(234);
@@ -72,7 +72,7 @@ describe('initGroupMembersApp', () => {
it('parses and sets `members` in Vuex store', () => {
setup();
- expect(vm.$store.state.members).toEqual(membersParsed);
+ expect(vm.$store.state.members).toEqual(members);
});
it('sets `tableFields` in Vuex store', () => {
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index e668f2a1998..fa324ce1cf9 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -4,6 +4,7 @@ export const member = {
canRemove: false,
canOverride: false,
isOverridden: false,
+ isDirectMember: false,
accessLevel: { integerValue: 50, stringValue: 'Owner' },
source: {
id: 178,
@@ -69,3 +70,8 @@ export const accessRequest = {
};
export const members = [member];
+
+export const membersJsonString = JSON.stringify(members);
+
+export const directMember = { ...member, isDirectMember: true };
+export const inheritedMember = { ...member, isDirectMember: false };
diff --git a/spec/frontend/members/store/actions_spec.js b/spec/frontend/members/store/actions_spec.js
index 5424fee0750..d913c5c56df 100644
--- a/spec/frontend/members/store/actions_spec.js
+++ b/spec/frontend/members/store/actions_spec.js
@@ -1,17 +1,17 @@
-import { noop } from 'lodash';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { members, group } from 'jest/members/mock_data';
-import testAction from 'helpers/vuex_action_helper';
+import { noop } from 'lodash';
import { useFakeDate } from 'helpers/fake_date';
+import testAction from 'helpers/vuex_action_helper';
+import { members, group } from 'jest/members/mock_data';
import httpStatusCodes from '~/lib/utils/http_status';
-import * as types from '~/members/store/mutation_types';
import {
updateMemberRole,
showRemoveGroupLinkModal,
hideRemoveGroupLinkModal,
updateMemberExpiration,
} from '~/members/store/actions';
+import * as types from '~/members/store/mutation_types';
describe('Vuex members actions', () => {
describe('update member actions', () => {
@@ -57,15 +57,17 @@ describe('Vuex members actions', () => {
describe('unsuccessful request', () => {
it(`commits ${types.RECEIVE_MEMBER_ROLE_ERROR} mutation and throws error`, async () => {
- mock.onPut().networkError();
+ const error = new Error('Network Error');
+ mock.onPut().reply(() => Promise.reject(error));
await expect(
testAction(updateMemberRole, payload, state, [
{
type: types.RECEIVE_MEMBER_ROLE_ERROR,
+ payload: { error },
},
]),
- ).rejects.toThrowError(new Error('Network Error'));
+ ).rejects.toThrowError(error);
});
});
});
@@ -108,15 +110,17 @@ describe('Vuex members actions', () => {
describe('unsuccessful request', () => {
it(`commits ${types.RECEIVE_MEMBER_EXPIRATION_ERROR} mutation and throws error`, async () => {
- mock.onPut().networkError();
+ const error = new Error('Network Error');
+ mock.onPut().reply(() => Promise.reject(error));
await expect(
testAction(updateMemberExpiration, { memberId, expiresAt }, state, [
{
type: types.RECEIVE_MEMBER_EXPIRATION_ERROR,
+ payload: { error },
},
]),
- ).rejects.toThrowError(new Error('Network Error'));
+ ).rejects.toThrowError(error);
});
});
});
diff --git a/spec/frontend/members/store/mutations_spec.js b/spec/frontend/members/store/mutations_spec.js
index 488bfdf15fd..7ad7034eb6d 100644
--- a/spec/frontend/members/store/mutations_spec.js
+++ b/spec/frontend/members/store/mutations_spec.js
@@ -1,6 +1,6 @@
import { members, group } from 'jest/members/mock_data';
-import mutations from '~/members/store/mutations';
import * as types from '~/members/store/mutation_types';
+import mutations from '~/members/store/mutations';
describe('Vuex members mutations', () => {
describe('update member mutations', () => {
@@ -28,13 +28,33 @@ describe('Vuex members mutations', () => {
});
describe(types.RECEIVE_MEMBER_ROLE_ERROR, () => {
- it('shows error message', () => {
- mutations[types.RECEIVE_MEMBER_ROLE_ERROR](state);
+ describe('when error does not have a message', () => {
+ it('shows default error message', () => {
+ mutations[types.RECEIVE_MEMBER_ROLE_ERROR](state, {
+ error: new Error('Network Error'),
+ });
+
+ expect(state.showError).toBe(true);
+ expect(state.errorMessage).toBe(
+ "An error occurred while updating the member's role, please try again.",
+ );
+ });
+ });
+
+ describe('when error has a message', () => {
+ it('shows error message', () => {
+ const error = new Error('Request failed with status code 422');
+ const message =
+ 'User email "john.smith@gmail.com" does not match the allowed domain of example.com';
+
+ error.response = {
+ data: { message },
+ };
+ mutations[types.RECEIVE_MEMBER_ROLE_ERROR](state, { error });
- expect(state.showError).toBe(true);
- expect(state.errorMessage).toBe(
- "An error occurred while updating the member's role, please try again.",
- );
+ expect(state.showError).toBe(true);
+ expect(state.errorMessage).toBe(message);
+ });
});
});
@@ -52,13 +72,33 @@ describe('Vuex members mutations', () => {
});
describe(types.RECEIVE_MEMBER_EXPIRATION_ERROR, () => {
- it('shows error message', () => {
- mutations[types.RECEIVE_MEMBER_EXPIRATION_ERROR](state);
+ describe('when error does not have a message', () => {
+ it('shows default error message', () => {
+ mutations[types.RECEIVE_MEMBER_EXPIRATION_ERROR](state, {
+ error: new Error('Network Error'),
+ });
+
+ expect(state.showError).toBe(true);
+ expect(state.errorMessage).toBe(
+ "An error occurred while updating the member's expiration date, please try again.",
+ );
+ });
+ });
- expect(state.showError).toBe(true);
- expect(state.errorMessage).toBe(
- "An error occurred while updating the member's expiration date, please try again.",
- );
+ describe('when error has a message', () => {
+ it('shows error message', () => {
+ const error = new Error('Request failed with status code 422');
+ const message =
+ 'User email "john.smith@gmail.com" does not match the allowed domain of example.com';
+
+ error.response = {
+ data: { message },
+ };
+ mutations[types.RECEIVE_MEMBER_EXPIRATION_ERROR](state, { error });
+
+ expect(state.showError).toBe(true);
+ expect(state.errorMessage).toBe(message);
+ });
});
});
});
diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js
index 7cd4e735b55..f447a4c4ee9 100644
--- a/spec/frontend/members/utils_spec.js
+++ b/spec/frontend/members/utils_spec.js
@@ -1,3 +1,4 @@
+import { DEFAULT_SORT } from '~/members/constants';
import {
generateBadges,
isGroup,
@@ -9,12 +10,19 @@ import {
canOverride,
parseSortParam,
buildSortHref,
+ parseDataAttributes,
+ groupLinkRequestFormatter,
} from '~/members/utils';
-import { DEFAULT_SORT } from '~/members/constants';
-import { member as memberMock, group, invite } from './mock_data';
+import {
+ member as memberMock,
+ directMember,
+ inheritedMember,
+ group,
+ invite,
+ membersJsonString,
+ members,
+} from './mock_data';
-const DIRECT_MEMBER_ID = 178;
-const INHERITED_MEMBER_ID = 179;
const IS_CURRENT_USER_ID = 123;
const IS_NOT_CURRENT_USER_ID = 124;
const URL_HOST = 'https://localhost/';
@@ -57,11 +65,11 @@ describe('Members Utils', () => {
describe('isDirectMember', () => {
test.each`
- sourceId | expected
- ${DIRECT_MEMBER_ID} | ${true}
- ${INHERITED_MEMBER_ID} | ${false}
- `('returns $expected', ({ sourceId, expected }) => {
- expect(isDirectMember(memberMock, sourceId)).toBe(expected);
+ member | expected
+ ${directMember} | ${true}
+ ${inheritedMember} | ${false}
+ `('returns $expected', ({ member, expected }) => {
+ expect(isDirectMember(member)).toBe(expected);
});
});
@@ -76,18 +84,13 @@ describe('Members Utils', () => {
});
describe('canRemove', () => {
- const memberCanRemove = {
- ...memberMock,
- canRemove: true,
- };
-
test.each`
- member | sourceId | expected
- ${memberCanRemove} | ${DIRECT_MEMBER_ID} | ${true}
- ${memberCanRemove} | ${INHERITED_MEMBER_ID} | ${false}
- ${memberMock} | ${INHERITED_MEMBER_ID} | ${false}
- `('returns $expected', ({ member, sourceId, expected }) => {
- expect(canRemove(member, sourceId)).toBe(expected);
+ member | expected
+ ${{ ...directMember, canRemove: true }} | ${true}
+ ${{ ...inheritedMember, canRemove: true }} | ${false}
+ ${{ ...memberMock, canRemove: false }} | ${false}
+ `('returns $expected', ({ member, expected }) => {
+ expect(canRemove(member)).toBe(expected);
});
});
@@ -96,25 +99,20 @@ describe('Members Utils', () => {
member | expected
${invite} | ${true}
${{ ...invite, invite: { ...invite.invite, canResend: false } }} | ${false}
- `('returns $expected', ({ member, sourceId, expected }) => {
- expect(canResend(member, sourceId)).toBe(expected);
+ `('returns $expected', ({ member, expected }) => {
+ expect(canResend(member)).toBe(expected);
});
});
describe('canUpdate', () => {
- const memberCanUpdate = {
- ...memberMock,
- canUpdate: true,
- };
-
test.each`
- member | currentUserId | sourceId | expected
- ${memberCanUpdate} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${true}
- ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false}
- ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${INHERITED_MEMBER_ID} | ${false}
- ${memberMock} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false}
- `('returns $expected', ({ member, currentUserId, sourceId, expected }) => {
- expect(canUpdate(member, currentUserId, sourceId)).toBe(expected);
+ member | currentUserId | expected
+ ${{ ...directMember, canUpdate: true }} | ${IS_NOT_CURRENT_USER_ID} | ${true}
+ ${{ ...directMember, canUpdate: true }} | ${IS_CURRENT_USER_ID} | ${false}
+ ${{ ...inheritedMember, canUpdate: true }} | ${IS_CURRENT_USER_ID} | ${false}
+ ${{ ...directMember, canUpdate: false }} | ${IS_NOT_CURRENT_USER_ID} | ${false}
+ `('returns $expected', ({ member, currentUserId, expected }) => {
+ expect(canUpdate(member, currentUserId)).toBe(expected);
});
});
@@ -229,4 +227,38 @@ describe('Members Utils', () => {
});
});
});
+
+ describe('parseDataAttributes', () => {
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ el.setAttribute('data-members', membersJsonString);
+ el.setAttribute('data-source-id', '234');
+ el.setAttribute('data-can-manage-members', 'true');
+ });
+
+ afterEach(() => {
+ el = null;
+ });
+
+ it('correctly parses the data attributes', () => {
+ expect(parseDataAttributes(el)).toEqual({
+ members,
+ sourceId: 234,
+ canManageMembers: true,
+ });
+ });
+ });
+
+ describe('groupLinkRequestFormatter', () => {
+ it('returns expected format', () => {
+ expect(
+ groupLinkRequestFormatter({
+ accessLevel: 50,
+ expires_at: '2020-10-16',
+ }),
+ ).toEqual({ group_link: { group_access: 50, expires_at: '2020-10-16' } });
+ });
+ });
});
diff --git a/spec/frontend/merge_request/components/status_box_spec.js b/spec/frontend/merge_request/components/status_box_spec.js
index e6b6512476b..9212ae19c2d 100644
--- a/spec/frontend/merge_request/components/status_box_spec.js
+++ b/spec/frontend/merge_request/components/status_box_spec.js
@@ -1,6 +1,6 @@
-import { nextTick } from 'vue';
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import StatusBox from '~/merge_request/components/status_box.vue';
import mrEventHub from '~/merge_request/eventhub';
@@ -18,6 +18,12 @@ const testCases = [
icon: 'issue-open-m',
},
{
+ name: 'Open',
+ state: 'locked',
+ class: 'status-box-open',
+ icon: 'issue-open-m',
+ },
+ {
name: 'Closed',
state: 'closed',
class: 'status-box-mr-closed',
diff --git a/spec/frontend/merge_request_spec.js b/spec/frontend/merge_request_spec.js
index 5624043260a..84647a108b2 100644
--- a/spec/frontend/merge_request_spec.js
+++ b/spec/frontend/merge_request_spec.js
@@ -1,5 +1,5 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import MergeRequest from '~/merge_request';
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 82462036219..fd2c240aff3 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -1,9 +1,8 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import initMrPage from 'helpers/init_vue_mr_page_helper';
import axios from '~/lib/utils/axios_utils';
import MergeRequestTabs from '~/merge_request_tabs';
-import '~/commit/pipelines/pipelines_bundle';
import '~/lib/utils/common_utils';
jest.mock('~/lib/utils/webpack', () => ({
diff --git a/spec/frontend/milestones/milestone_combobox_spec.js b/spec/frontend/milestones/milestone_combobox_spec.js
index 8c519abe382..4d1a0a0a440 100644
--- a/spec/frontend/milestones/milestone_combobox_spec.js
+++ b/spec/frontend/milestones/milestone_combobox_spec.js
@@ -1,13 +1,13 @@
-import Vue, { nextTick } from 'vue';
-import Vuex from 'vuex';
+import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
import { ENTER_KEY } from '~/lib/utils/keys';
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
-import { projectMilestones, groupMilestones } from './mock_data';
import createStore from '~/milestones/stores/';
+import { projectMilestones, groupMilestones } from './mock_data';
const extraLinks = [
{ text: 'Create new', url: 'http://127.0.0.1:3000/h5bp/html5-boilerplate/-/milestones/new' },
diff --git a/spec/frontend/milestones/stores/actions_spec.js b/spec/frontend/milestones/stores/actions_spec.js
index a62b0c49a80..4355ea71fb2 100644
--- a/spec/frontend/milestones/stores/actions_spec.js
+++ b/spec/frontend/milestones/stores/actions_spec.js
@@ -1,7 +1,7 @@
import testAction from 'helpers/vuex_action_helper';
-import createState from '~/milestones/stores/state';
import * as actions from '~/milestones/stores/actions';
import * as types from '~/milestones/stores/mutation_types';
+import createState from '~/milestones/stores/state';
let mockProjectMilestonesReturnValue;
let mockGroupMilestonesReturnValue;
diff --git a/spec/frontend/milestones/stores/mutations_spec.js b/spec/frontend/milestones/stores/mutations_spec.js
index 0b69a9d572d..91b2acf23c5 100644
--- a/spec/frontend/milestones/stores/mutations_spec.js
+++ b/spec/frontend/milestones/stores/mutations_spec.js
@@ -1,6 +1,6 @@
-import createState from '~/milestones/stores/state';
-import mutations from '~/milestones/stores/mutations';
import * as types from '~/milestones/stores/mutation_types';
+import mutations from '~/milestones/stores/mutations';
+import createState from '~/milestones/stores/state';
describe('Milestones combobox Vuex store mutations', () => {
let state;
diff --git a/spec/frontend/mini_pipeline_graph_dropdown_spec.js b/spec/frontend/mini_pipeline_graph_dropdown_spec.js
index b21ddabbfb1..3ff34c967e4 100644
--- a/spec/frontend/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/frontend/mini_pipeline_graph_dropdown_spec.js
@@ -1,5 +1,5 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
diff --git a/spec/frontend/monitoring/alert_widget_spec.js b/spec/frontend/monitoring/alert_widget_spec.js
index fd9fb374274..1f0597bac67 100644
--- a/spec/frontend/monitoring/alert_widget_spec.js
+++ b/spec/frontend/monitoring/alert_widget_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlTooltip, GlSprintf, GlBadge } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
-import AlertWidget from '~/monitoring/components/alert_widget.vue';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import AlertWidget from '~/monitoring/components/alert_widget.vue';
const mockReadAlert = jest.fn();
const mockCreateAlert = jest.fn();
diff --git a/spec/frontend/monitoring/components/alert_widget_form_spec.js b/spec/frontend/monitoring/components/alert_widget_form_spec.js
index 6d87fb85f4d..e0ef1040f6b 100644
--- a/spec/frontend/monitoring/components/alert_widget_form_spec.js
+++ b/spec/frontend/monitoring/components/alert_widget_form_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import INVALID_URL from '~/lib/utils/invalid_url';
import AlertWidgetForm from '~/monitoring/components/alert_widget_form.vue';
import ModalStub from '../stubs/modal_stub';
diff --git a/spec/frontend/monitoring/components/charts/anomaly_spec.js b/spec/frontend/monitoring/components/charts/anomaly_spec.js
index dad3003d536..c44fd8dce33 100644
--- a/spec/frontend/monitoring/components/charts/anomaly_spec.js
+++ b/spec/frontend/monitoring/components/charts/anomaly_spec.js
@@ -2,10 +2,10 @@ import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import Anomaly from '~/monitoring/components/charts/anomaly.vue';
+import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import { colorValues } from '~/monitoring/constants';
-import { anomalyDeploymentData, mockProjectDir } from '../../mock_data';
import { anomalyGraphData } from '../../graph_data';
-import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
+import { anomalyDeploymentData, mockProjectDir } from '../../mock_data';
const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
diff --git a/spec/frontend/monitoring/components/charts/bar_spec.js b/spec/frontend/monitoring/components/charts/bar_spec.js
index 40edde5f666..6368c53943a 100644
--- a/spec/frontend/monitoring/components/charts/bar_spec.js
+++ b/spec/frontend/monitoring/components/charts/bar_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlBarChart } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
import Bar from '~/monitoring/components/charts/bar.vue';
import { barGraphData } from '../../graph_data';
diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js
index 0c4b6e8990d..e10cb3a456a 100644
--- a/spec/frontend/monitoring/components/charts/column_spec.js
+++ b/spec/frontend/monitoring/components/charts/column_spec.js
@@ -1,6 +1,6 @@
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock';
-import { GlColumnChart } from '@gitlab/ui/dist/charts';
import ColumnChart from '~/monitoring/components/charts/column.vue';
jest.mock('~/lib/utils/icon_utils', () => ({
diff --git a/spec/frontend/monitoring/components/charts/gauge_spec.js b/spec/frontend/monitoring/components/charts/gauge_spec.js
index 9215f2e411f..c8f67d5d8c7 100644
--- a/spec/frontend/monitoring/components/charts/gauge_spec.js
+++ b/spec/frontend/monitoring/components/charts/gauge_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlGaugeChart } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
import GaugeChart from '~/monitoring/components/charts/gauge.vue';
import { gaugeChartGraphData } from '../../graph_data';
diff --git a/spec/frontend/monitoring/components/charts/heatmap_spec.js b/spec/frontend/monitoring/components/charts/heatmap_spec.js
index c8375810a7b..841b7e0648a 100644
--- a/spec/frontend/monitoring/components/charts/heatmap_spec.js
+++ b/spec/frontend/monitoring/components/charts/heatmap_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlHeatmap } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock';
import Heatmap from '~/monitoring/components/charts/heatmap.vue';
import { heatmapGraphData } from '../../graph_data';
diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js
index 37712eb3012..8633b49565f 100644
--- a/spec/frontend/monitoring/components/charts/single_stat_spec.js
+++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
import { singleStatGraphData } from '../../graph_data';
@@ -27,8 +27,12 @@ describe('Single Stat Chart component', () => {
describe('computed', () => {
describe('statValue', () => {
- it('should interpolate the value and unit props', () => {
- expect(findChart().props('value')).toBe('1.00MB');
+ it('should display the correct value', () => {
+ expect(findChart().props('value')).toBe('1.00');
+ });
+
+ it('should display the correct value unit', () => {
+ expect(findChart().props('unit')).toBe('MB');
});
it('should change the value representation to a percentile one', () => {
@@ -36,7 +40,8 @@ describe('Single Stat Chart component', () => {
graphData: singleStatGraphData({ max_value: 120 }, { value: 91 }),
});
- expect(findChart().props('value')).toContain('75.83%');
+ expect(findChart().props('value')).toBe('75.83');
+ expect(findChart().props('unit')).toBe('%');
});
it('should display NaN for non numeric maxValue values', () => {
diff --git a/spec/frontend/monitoring/components/charts/stacked_column_spec.js b/spec/frontend/monitoring/components/charts/stacked_column_spec.js
index 4d4224a8b11..f47728313c6 100644
--- a/spec/frontend/monitoring/components/charts/stacked_column_spec.js
+++ b/spec/frontend/monitoring/components/charts/stacked_column_spec.js
@@ -1,7 +1,7 @@
+import { GlStackedColumnChart, GlChartLegend } from '@gitlab/ui/dist/charts';
import { shallowMount, mount } from '@vue/test-utils';
-import timezoneMock from 'timezone-mock';
import { cloneDeep } from 'lodash';
-import { GlStackedColumnChart, GlChartLegend } from '@gitlab/ui/dist/charts';
+import timezoneMock from 'timezone-mock';
import StackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
import { stackedColumnGraphData } from '../../graph_data';
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index b7e1cb91987..afa63bcff29 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -1,17 +1,18 @@
-import { mount, shallowMount } from '@vue/test-utils';
-import { setTestTimeout } from 'helpers/timeout';
-import timezoneMock from 'timezone-mock';
import { GlLink } from '@gitlab/ui';
-import { TEST_HOST } from 'helpers/test_constants';
import {
GlAreaChart,
GlLineChart,
GlChartSeriesLabel,
GlChartLegend,
} from '@gitlab/ui/dist/charts';
+import { mount, shallowMount } from '@vue/test-utils';
+import timezoneMock from 'timezone-mock';
+import { TEST_HOST } from 'helpers/test_constants';
+import { setTestTimeout } from 'helpers/timeout';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
-import { panelTypes, chartHeight } from '~/monitoring/constants';
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
+import { panelTypes, chartHeight } from '~/monitoring/constants';
+import { timeSeriesGraphData } from '../../graph_data';
import {
deploymentData,
mockProjectDir,
@@ -19,8 +20,6 @@ import {
mockFixedTimeRange,
} from '../../mock_data';
-import { timeSeriesGraphData } from '../../graph_data';
-
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
jest.fn((func) => {
diff --git a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
index d1028445638..8202d423ff3 100644
--- a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
+++ b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
describe('Create dashboard modal', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
index 43d5937a3a1..6e98ca28071 100644
--- a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
@@ -1,14 +1,14 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
-import { createStore } from '~/monitoring/stores';
-import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
-import { setupAllDashboards, setupStoreWithData } from '../store_utils';
+import { shallowMount } from '@vue/test-utils';
+import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import { redirectTo } from '~/lib/utils/url_utility';
-import Tracking from '~/tracking';
import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue';
-import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
-import { dashboardActionsMenuProps, dashboardGitResponse } from '../mock_data';
+import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
+import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
+import Tracking from '~/tracking';
+import { dashboardActionsMenuProps, dashboardGitResponse } from '../mock_data';
+import { setupAllDashboards, setupStoreWithData } from '../store_utils';
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
index 32fd9c45e8d..8be7d641953 100644
--- a/spec/frontend/monitoring/components/dashboard_header_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -1,20 +1,20 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem, GlSearchBoxByType, GlLoadingIcon, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { redirectTo } from '~/lib/utils/url_utility';
+import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue';
+import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
+import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
+import RefreshButton from '~/monitoring/components/refresh_button.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
-import RefreshButton from '~/monitoring/components/refresh_button.vue';
-import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
-import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
-import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue';
-import { setupAllDashboards, setupStoreWithDashboard, setupStoreWithData } from '../store_utils';
import {
environmentData,
dashboardGitResponse,
selfMonitoringDashboardGitResponse,
dashboardHeaderProps,
} from '../mock_data';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { setupAllDashboards, setupStoreWithDashboard, setupStoreWithData } from '../store_utils';
const mockProjectPath = 'https://path/to/project';
diff --git a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
index 08c69701bd2..b794d0c571e 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
@@ -1,14 +1,13 @@
-import { shallowMount } from '@vue/test-utils';
import { GlCard, GlForm, GlFormTextarea, GlAlert } from '@gitlab/ui';
-import { createStore } from '~/monitoring/stores';
+import { shallowMount } from '@vue/test-utils';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
+import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
+import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
+import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { metricsDashboardResponse } from '../fixture_data';
import { mockTimeRange } from '../mock_data';
-import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
-import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
-
const mockPanel = metricsDashboardResponse.dashboard.panel_groups[0].panels[0];
describe('dashboard invalid url parameters', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index f64e05d3a2c..a72dbbd0f41 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -1,13 +1,32 @@
-import Vuex from 'vuex';
+import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
import { setTestTimeout } from 'helpers/timeout';
-import { GlDropdownItem } from '@gitlab/ui';
-import invalidUrl from '~/lib/utils/invalid_url';
import axios from '~/lib/utils/axios_utils';
+import invalidUrl from '~/lib/utils/invalid_url';
import AlertWidget from '~/monitoring/components/alert_widget.vue';
+import MonitorAnomalyChart from '~/monitoring/components/charts/anomaly.vue';
+import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
+import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
+import MonitorEmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import MonitorHeatmapChart from '~/monitoring/components/charts/heatmap.vue';
+import MonitorSingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
+import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
+import { panelTypes } from '~/monitoring/constants';
+
+import { createStore, monitoringDashboard } from '~/monitoring/stores';
+import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
+import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
+import {
+ anomalyGraphData,
+ singleStatGraphData,
+ heatmapGraphData,
+ barGraphData,
+} from '../graph_data';
import {
mockAlert,
mockLogsHref,
@@ -16,27 +35,6 @@ import {
mockNamespacedData,
mockTimeRange,
} from '../mock_data';
-import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
-import {
- anomalyGraphData,
- singleStatGraphData,
- heatmapGraphData,
- barGraphData,
-} from '../graph_data';
-
-import { panelTypes } from '~/monitoring/constants';
-
-import MonitorEmptyChart from '~/monitoring/components/charts/empty_chart.vue';
-import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
-import MonitorAnomalyChart from '~/monitoring/components/charts/anomaly.vue';
-import MonitorSingleStatChart from '~/monitoring/components/charts/single_stat.vue';
-import MonitorHeatmapChart from '~/monitoring/components/charts/heatmap.vue';
-import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
-import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
-import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
-
-import { createStore, monitoringDashboard } from '~/monitoring/stores';
-import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
const mocks = {
$toast: {
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index db35f1cdde3..5c7042d4cb5 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -1,22 +1,29 @@
import { shallowMount, mount } from '@vue/test-utils';
-import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
+import VueDraggable from 'vuedraggable';
import { TEST_HOST } from 'helpers/test_constants';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { ESC_KEY } from '~/lib/utils/keys';
import { objectToQuery } from '~/lib/utils/url_utility';
-import axios from '~/lib/utils/axios_utils';
-import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
-import EmptyState from '~/monitoring/components/empty_state.vue';
-import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
+import EmptyState from '~/monitoring/components/empty_state.vue';
import GraphGroup from '~/monitoring/components/graph_group.vue';
+import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import LinksSection from '~/monitoring/components/links_section.vue';
+import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import {
+ metricsDashboardViewModel,
+ metricsDashboardPanelCount,
+ dashboardProps,
+} from '../fixture_data';
+import { dashboardGitResponse, storeVariables } from '../mock_data';
+import {
setupAllDashboards,
setupStoreWithDashboard,
setMetricResult,
@@ -24,13 +31,6 @@ import {
setupStoreWithDataForPanelCount,
setupStoreWithLinks,
} from '../store_utils';
-import { dashboardGitResponse, storeVariables } from '../mock_data';
-import {
- metricsDashboardViewModel,
- metricsDashboardPanelCount,
- dashboardProps,
-} from '../fixture_data';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
diff --git a/spec/frontend/monitoring/components/dashboard_template_spec.js b/spec/frontend/monitoring/components/dashboard_template_spec.js
index 8941e57c4ce..4e220d724f4 100644
--- a/spec/frontend/monitoring/components/dashboard_template_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_template_spec.js
@@ -4,8 +4,8 @@ import axios from '~/lib/utils/axios_utils';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
-import { setupAllDashboards } from '../store_utils';
import { dashboardProps } from '../fixture_data';
+import { setupAllDashboards } from '../store_utils';
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index c4630bde32f..9830b6d047f 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -1,6 +1,7 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import {
queryToObject,
redirectTo,
@@ -8,14 +9,13 @@ import {
mergeUrlParams,
updateHistory,
} from '~/lib/utils/url_utility';
-import axios from '~/lib/utils/axios_utils';
-import { mockProjectDir } from '../mock_data';
-import { dashboardProps } from '../fixture_data';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
import { defaultTimeRange } from '~/vue_shared/constants';
+import { dashboardProps } from '../fixture_data';
+import { mockProjectDir } from '../mock_data';
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index 6ed190051ce..c9241834789 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -1,6 +1,6 @@
-import { nextTick } from 'vue';
-import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
index 4abb5b2e27b..9672f6a315a 100644
--- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
@@ -1,5 +1,5 @@
-import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
import { dashboardGitResponse } from '../mock_data';
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
index 7e7065da179..1bc89e509b5 100644
--- a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
@@ -1,12 +1,12 @@
-import Vuex from 'vuex';
-import Vue from 'vue';
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
-import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
import { dashboardGitResponse } from '../mock_data';
diff --git a/spec/frontend/monitoring/components/embeds/embed_group_spec.js b/spec/frontend/monitoring/components/embeds/embed_group_spec.js
index cb06a1a6b64..79b223d96e4 100644
--- a/spec/frontend/monitoring/components/embeds/embed_group_spec.js
+++ b/spec/frontend/monitoring/components/embeds/embed_group_spec.js
@@ -1,6 +1,6 @@
+import { GlButton, GlCard } from '@gitlab/ui';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlButton, GlCard } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
diff --git a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
index 74f265930b1..90647f50b14 100644
--- a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
+++ b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
@@ -1,7 +1,7 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { TEST_HOST } from 'helpers/test_constants';
import { setHTMLFixture } from 'helpers/fixtures';
+import { TEST_HOST } from 'helpers/test_constants';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
diff --git a/spec/frontend/monitoring/components/empty_state_spec.js b/spec/frontend/monitoring/components/empty_state_spec.js
index abb8b21e9f4..1ecb101574b 100644
--- a/spec/frontend/monitoring/components/empty_state_spec.js
+++ b/spec/frontend/monitoring/components/empty_state_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
-import { dashboardEmptyStates } from '~/monitoring/constants';
+import { shallowMount } from '@vue/test-utils';
import EmptyState from '~/monitoring/components/empty_state.vue';
+import { dashboardEmptyStates } from '~/monitoring/constants';
function createComponent(props) {
return shallowMount(EmptyState, {
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index c57461c2d09..625dd3f0b33 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import GraphGroup from '~/monitoring/components/graph_group.vue';
describe('Graph group component', () => {
diff --git a/spec/frontend/monitoring/components/links_section_spec.js b/spec/frontend/monitoring/components/links_section_spec.js
index 2daad77d513..8fc287c50e4 100644
--- a/spec/frontend/monitoring/components/links_section_spec.js
+++ b/spec/frontend/monitoring/components/links_section_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
-import { createStore } from '~/monitoring/stores';
+import { shallowMount } from '@vue/test-utils';
import LinksSection from '~/monitoring/components/links_section.vue';
+import { createStore } from '~/monitoring/stores';
describe('Links Section component', () => {
let store;
diff --git a/spec/frontend/monitoring/components/refresh_button_spec.js b/spec/frontend/monitoring/components/refresh_button_spec.js
index a03d29309d4..248cf32d54b 100644
--- a/spec/frontend/monitoring/components/refresh_button_spec.js
+++ b/spec/frontend/monitoring/components/refresh_button_spec.js
@@ -1,8 +1,8 @@
+import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Visibility from 'visibilityjs';
-import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
-import { createStore } from '~/monitoring/stores';
import RefreshButton from '~/monitoring/components/refresh_button.vue';
+import { createStore } from '~/monitoring/stores';
describe('RefreshButton', () => {
let wrapper;
diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
index f5db17ce367..f5ee32e78e6 100644
--- a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
describe('Custom variable component', () => {
diff --git a/spec/frontend/monitoring/components/variables/text_field_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
index 99c6facac38..28e02dff4bf 100644
--- a/spec/frontend/monitoring/components/variables/text_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import TextField from '~/monitoring/components/variables/text_field.vue';
describe('Text variable component', () => {
diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js
index 3097906ee68..6157de0dafe 100644
--- a/spec/frontend/monitoring/components/variables_section_spec.js
+++ b/spec/frontend/monitoring/components/variables_section_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
-import VariablesSection from '~/monitoring/components/variables_section.vue';
+import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
import TextField from '~/monitoring/components/variables/text_field.vue';
-import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
+import VariablesSection from '~/monitoring/components/variables_section.vue';
import { createStore } from '~/monitoring/stores';
import { convertVariablesForURL } from '~/monitoring/utils';
import { storeVariables } from '../mock_data';
diff --git a/spec/frontend/monitoring/csv_export_spec.js b/spec/frontend/monitoring/csv_export_spec.js
index eb2a6e40243..42d19c21a7b 100644
--- a/spec/frontend/monitoring/csv_export_spec.js
+++ b/spec/frontend/monitoring/csv_export_spec.js
@@ -1,5 +1,5 @@
-import { timeSeriesGraphData } from './graph_data';
import { graphDataToCsv } from '~/monitoring/csv_export';
+import { timeSeriesGraphData } from './graph_data';
describe('monitoring export_csv', () => {
describe('graphDataToCsv', () => {
diff --git a/spec/frontend/monitoring/fixture_data.js b/spec/frontend/monitoring/fixture_data.js
index 0f3dbaac493..d20a111c701 100644
--- a/spec/frontend/monitoring/fixture_data.js
+++ b/spec/frontend/monitoring/fixture_data.js
@@ -1,7 +1,7 @@
-import { stateAndPropsFromDataset } from '~/monitoring/utils';
-import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
-import { metricStates } from '~/monitoring/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { metricStates } from '~/monitoring/constants';
+import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
+import { stateAndPropsFromDataset } from '~/monitoring/utils';
import { metricsResult } from './mock_data';
diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js
index 0b23ee41927..981955efebb 100644
--- a/spec/frontend/monitoring/graph_data.js
+++ b/spec/frontend/monitoring/graph_data.js
@@ -1,5 +1,5 @@
-import { mapPanelToViewModel, normalizeQueryResponseData } from '~/monitoring/stores/utils';
import { panelTypes, metricStates } from '~/monitoring/constants';
+import { mapPanelToViewModel, normalizeQueryResponseData } from '~/monitoring/stores/utils';
const initTime = 1435781450; // "Wed, 01 Jul 2015 20:10:50 GMT"
const intervalSeconds = 120;
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index ca06c96c7d6..29a7c86491d 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -1,6 +1,6 @@
// The path below needs to be relative because we import the mock-data to karma
-import { TEST_HOST } from '../__helpers__/test_constants';
import invalidUrl from '~/lib/utils/invalid_url';
+import { TEST_HOST } from '../__helpers__/test_constants';
// This import path needs to be relative for now because this mock data is used in
// Karma specs too, where the helpers/test_constants alias can not be resolved
diff --git a/spec/frontend/monitoring/pages/dashboard_page_spec.js b/spec/frontend/monitoring/pages/dashboard_page_spec.js
index 675165e9e56..dbe9cc21ad5 100644
--- a/spec/frontend/monitoring/pages/dashboard_page_spec.js
+++ b/spec/frontend/monitoring/pages/dashboard_page_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { createStore } from '~/monitoring/stores';
-import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
import Dashboard from '~/monitoring/components/dashboard.vue';
+import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
+import { createStore } from '~/monitoring/stores';
import { dashboardProps } from '../fixture_data';
describe('monitoring/pages/dashboard_page', () => {
diff --git a/spec/frontend/monitoring/pages/panel_new_page_spec.js b/spec/frontend/monitoring/pages/panel_new_page_spec.js
index 83365b754d9..c89cbc52bcb 100644
--- a/spec/frontend/monitoring/pages/panel_new_page_spec.js
+++ b/spec/frontend/monitoring/pages/panel_new_page_spec.js
@@ -1,10 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
-import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
-import { createStore } from '~/monitoring/stores';
+import { shallowMount } from '@vue/test-utils';
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
-
import PanelNewPage from '~/monitoring/pages/panel_new_page.vue';
+import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
+import { createStore } from '~/monitoring/stores';
const dashboard = 'dashboard.yml';
diff --git a/spec/frontend/monitoring/requests/index_spec.js b/spec/frontend/monitoring/requests/index_spec.js
index 078de5f15d1..b30b1e60575 100644
--- a/spec/frontend/monitoring/requests/index_spec.js
+++ b/spec/frontend/monitoring/requests/index_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import { backoffMockImplementation } from 'helpers/backoff_helper';
import axios from '~/lib/utils/axios_utils';
-import statusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils';
-import { metricsDashboardResponse } from '../fixture_data';
+import statusCodes from '~/lib/utils/http_status';
import { getDashboard, getPrometheusQueryData } from '~/monitoring/requests';
+import { metricsDashboardResponse } from '../fixture_data';
describe('monitoring metrics_requests', () => {
let mock;
diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js
index c30c4c56a6c..b027d60f61e 100644
--- a/spec/frontend/monitoring/router_spec.js
+++ b/spec/frontend/monitoring/router_spec.js
@@ -1,10 +1,10 @@
import { mount, createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';
+import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
import PanelNewPage from '~/monitoring/pages/panel_new_page.vue';
-import Dashboard from '~/monitoring/components/dashboard.vue';
-import { createStore } from '~/monitoring/stores';
import createRouter from '~/monitoring/router';
+import { createStore } from '~/monitoring/stores';
import { dashboardProps } from './fixture_data';
import { dashboardHeaderProps } from './mock_data';
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 319441b5ba2..b7f741c449f 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -1,17 +1,16 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { backoffMockImplementation } from 'helpers/backoff_helper';
-import Tracking from '~/tracking';
+import testAction from 'helpers/vuex_action_helper';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import statusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { defaultTimeRange } from '~/vue_shared/constants';
-import * as getters from '~/monitoring/stores/getters';
+import statusCodes from '~/lib/utils/http_status';
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
+import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql';
+import getDashboardValidationWarnings from '~/monitoring/queries/getDashboardValidationWarnings.query.graphql';
+import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
import { createStore } from '~/monitoring/stores';
-import * as types from '~/monitoring/stores/mutation_types';
import {
setGettingStartedEmptyState,
setInitialState,
@@ -33,15 +32,21 @@ import {
fetchVariableMetricLabelValues,
fetchPanelPreview,
} from '~/monitoring/stores/actions';
+import * as getters from '~/monitoring/stores/getters';
+import * as types from '~/monitoring/stores/mutation_types';
+import storeState from '~/monitoring/stores/state';
import {
gqClient,
parseEnvironmentsResponse,
parseAnnotationsResponse,
} from '~/monitoring/stores/utils';
-import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
-import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql';
-import getDashboardValidationWarnings from '~/monitoring/queries/getDashboardValidationWarnings.query.graphql';
-import storeState from '~/monitoring/stores/state';
+import Tracking from '~/tracking';
+import { defaultTimeRange } from '~/vue_shared/constants';
+import {
+ metricsDashboardResponse,
+ metricsDashboardViewModel,
+ metricsDashboardPanelCount,
+} from '../fixture_data';
import {
deploymentData,
environmentData,
@@ -49,11 +54,6 @@ import {
dashboardGitResponse,
mockDashboardsErrorResponse,
} from '../mock_data';
-import {
- metricsDashboardResponse,
- metricsDashboardViewModel,
- metricsDashboardPanelCount,
-} from '../fixture_data';
jest.mock('~/flash');
diff --git a/spec/frontend/monitoring/store/embed_group/mutations_spec.js b/spec/frontend/monitoring/store/embed_group/mutations_spec.js
index a1d04e23e41..2f8d7687aad 100644
--- a/spec/frontend/monitoring/store/embed_group/mutations_spec.js
+++ b/spec/frontend/monitoring/store/embed_group/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/monitoring/stores/embed_group/state';
-import mutations from '~/monitoring/stores/embed_group/mutations';
import * as types from '~/monitoring/stores/embed_group/mutation_types';
+import mutations from '~/monitoring/stores/embed_group/mutations';
+import state from '~/monitoring/stores/embed_group/state';
import { mockNamespace } from '../../mock_data';
describe('Embed group mutations', () => {
diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js
index 771ec0ea549..c7f3bdbf1f8 100644
--- a/spec/frontend/monitoring/store/getters_spec.js
+++ b/spec/frontend/monitoring/store/getters_spec.js
@@ -1,8 +1,9 @@
import _ from 'lodash';
+import { metricStates } from '~/monitoring/constants';
import * as getters from '~/monitoring/stores/getters';
-import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
-import { metricStates } from '~/monitoring/constants';
+import mutations from '~/monitoring/stores/mutations';
+import { metricsDashboardPayload } from '../fixture_data';
import {
customDashboardBasePath,
environmentData,
@@ -11,7 +12,6 @@ import {
storeVariables,
mockLinks,
} from '../mock_data';
-import { metricsDashboardPayload } from '../fixture_data';
describe('Monitoring store Getters', () => {
let state;
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 571828eb67c..ae1a4e16b30 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -1,12 +1,12 @@
import httpStatusCodes from '~/lib/utils/http_status';
-import mutations from '~/monitoring/stores/mutations';
+import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
+import mutations from '~/monitoring/stores/mutations';
import state from '~/monitoring/stores/state';
-import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
-import { deploymentData, dashboardGitResponse, storeTextVariables } from '../mock_data';
-import { prometheusMatrixMultiResult } from '../graph_data';
import { metricsDashboardPayload } from '../fixture_data';
+import { prometheusMatrixMultiResult } from '../graph_data';
+import { deploymentData, dashboardGitResponse, storeTextVariables } from '../mock_data';
describe('Monitoring mutations', () => {
let stateCopy;
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 58bb87cb332..697bdb9185f 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -1,4 +1,6 @@
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
+import * as urlUtils from '~/lib/utils/url_utility';
+import { NOT_IN_DB_PREFIX } from '~/monitoring/constants';
import {
uniqMetricsId,
parseEnvironmentsResponse,
@@ -10,9 +12,7 @@ import {
addDashboardMetaDataToLink,
normalizeCustomDashboardPath,
} from '~/monitoring/stores/utils';
-import * as urlUtils from '~/lib/utils/url_utility';
import { annotationsData } from '../mock_data';
-import { NOT_IN_DB_PREFIX } from '~/monitoring/constants';
const projectPath = 'gitlab-org/gitlab-test';
diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js
index de124b0313c..58e7175c04c 100644
--- a/spec/frontend/monitoring/store/variable_mapping_spec.js
+++ b/spec/frontend/monitoring/store/variable_mapping_spec.js
@@ -1,3 +1,4 @@
+import * as urlUtils from '~/lib/utils/url_utility';
import {
parseTemplatingVariables,
mergeURLVariables,
@@ -9,7 +10,6 @@ import {
storeCustomVariables,
storeMetricLabelValuesVariables,
} from '../mock_data';
-import * as urlUtils from '~/lib/utils/url_utility';
describe('Monitoring variable mapping', () => {
describe('parseTemplatingVariables', () => {
diff --git a/spec/frontend/monitoring/store_utils.js b/spec/frontend/monitoring/store_utils.js
index 911ccc78f7b..96219661b9b 100644
--- a/spec/frontend/monitoring/store_utils.js
+++ b/spec/frontend/monitoring/store_utils.js
@@ -1,6 +1,6 @@
import * as types from '~/monitoring/stores/mutation_types';
-import { metricsResult, environmentData, dashboardGitResponse } from './mock_data';
import { metricsDashboardPayload } from './fixture_data';
+import { metricsResult, environmentData, dashboardGitResponse } from './mock_data';
export const setMetricResult = ({ store, result, group = 0, panel = 0, metric = 0 }) => {
const { dashboard } = store.state.monitoringDashboard;
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index cd49e4c7968..25ae4dcd702 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -1,9 +1,9 @@
import { TEST_HOST } from 'helpers/test_constants';
-import * as monitoringUtils from '~/monitoring/utils';
import * as urlUtils from '~/lib/utils/url_utility';
-import { mockProjectDir, barMockData } from './mock_data';
-import { singleStatGraphData, anomalyGraphData } from './graph_data';
+import * as monitoringUtils from '~/monitoring/utils';
import { metricsDashboardViewModel, graphData } from './fixture_data';
+import { singleStatGraphData, anomalyGraphData } from './graph_data';
+import { mockProjectDir, barMockData } from './mock_data';
const mockPath = `${TEST_HOST}${mockProjectDir}/-/environments/29/metrics`;
diff --git a/spec/frontend/namespace_select_spec.js b/spec/frontend/namespace_select_spec.js
index d6f3eb75cd9..a38508dd601 100644
--- a/spec/frontend/namespace_select_spec.js
+++ b/spec/frontend/namespace_select_spec.js
@@ -1,5 +1,5 @@
-import NamespaceSelect from '~/namespace_select';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
+import NamespaceSelect from '~/namespace_select';
jest.mock('~/deprecated_jquery_dropdown');
diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js
index ad33858da22..4d6addaf47c 100644
--- a/spec/frontend/notebook/cells/markdown_spec.js
+++ b/spec/frontend/notebook/cells/markdown_spec.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import katex from 'katex';
+import Vue from 'vue';
import MarkdownComponent from '~/notebook/cells/markdown.vue';
const Component = Vue.extend(MarkdownComponent);
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 002c4f206cb..2f58f75ab70 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -1,15 +1,15 @@
-import { nextTick } from 'vue';
import { mount, shallowMount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
import Autosize from 'autosize';
+import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { deprecatedCreateFlash as flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import createStore from '~/notes/stores';
import CommentForm from '~/notes/components/comment_form.vue';
import * as constants from '~/notes/constants';
import eventHub from '~/notes/event_hub';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import createStore from '~/notes/stores';
import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
jest.mock('autosize');
@@ -22,11 +22,25 @@ describe('issue_comment_form component', () => {
let wrapper;
let axiosMock;
- const findCloseReopenButton = () => wrapper.find('[data-testid="close-reopen-button"]');
+ const findCloseReopenButton = () => wrapper.findByTestId('close-reopen-button');
+ const findCommentButton = () => wrapper.findByTestId('comment-button');
+ const findTextArea = () => wrapper.findByTestId('comment-field');
+ const findConfidentialNoteCheckbox = () => wrapper.findByTestId('confidential-note-checkbox');
- const findCommentButton = () => wrapper.find('[data-testid="comment-button"]');
+ const createNotableDataMock = (data = {}) => {
+ return {
+ ...noteableDataMock,
+ ...data,
+ };
+ };
- const findTextArea = () => wrapper.find('[data-testid="comment-field"]');
+ const notableDataMockCanUpdateIssuable = createNotableDataMock({
+ current_user: { can_update: true, can_create_note: true },
+ });
+
+ const notableDataMockCannotUpdateIssuable = createNotableDataMock({
+ current_user: { can_update: false, can_create_note: true },
+ });
const mountComponent = ({
initialData = {},
@@ -34,23 +48,29 @@ describe('issue_comment_form component', () => {
noteableData = noteableDataMock,
notesData = notesDataMock,
userData = userDataMock,
+ features = {},
mountFunction = shallowMount,
} = {}) => {
store.dispatch('setNoteableData', noteableData);
store.dispatch('setNotesData', notesData);
store.dispatch('setUserData', userData);
- wrapper = mountFunction(CommentForm, {
- propsData: {
- noteableType,
- },
- data() {
- return {
- ...initialData,
- };
- },
- store,
- });
+ wrapper = extendedWrapper(
+ mountFunction(CommentForm, {
+ propsData: {
+ noteableType,
+ },
+ data() {
+ return {
+ ...initialData,
+ };
+ },
+ store,
+ provide: {
+ glFeatures: features,
+ },
+ }),
+ );
};
beforeEach(() => {
@@ -64,14 +84,6 @@ describe('issue_comment_form component', () => {
});
describe('user is logged in', () => {
- describe('avatar', () => {
- it('should render user avatar with link', () => {
- mountComponent({ mountFunction: mount });
-
- expect(wrapper.find(UserAvatarLink).attributes('href')).toBe(userDataMock.path);
- });
- });
-
describe('handleSave', () => {
it('should request to save note when note is entered', () => {
mountComponent({ mountFunction: mount, initialData: { note: 'hello world' } });
@@ -368,6 +380,83 @@ describe('issue_comment_form component', () => {
});
});
});
+
+ describe('confidential notes checkbox', () => {
+ describe('when confidentialNotes feature flag is `false`', () => {
+ const features = { confidentialNotes: false };
+
+ it('should not render checkbox', () => {
+ mountComponent({
+ mountFunction: mount,
+ initialData: { note: 'confidential note' },
+ noteableData: { ...notableDataMockCanUpdateIssuable },
+ features,
+ });
+
+ const checkbox = findConfidentialNoteCheckbox();
+ expect(checkbox.exists()).toBe(false);
+ });
+ });
+
+ describe('when confidentialNotes feature flag is `true`', () => {
+ const features = { confidentialNotes: true };
+
+ it('should render checkbox as unchecked by default', () => {
+ mountComponent({
+ mountFunction: mount,
+ initialData: { note: 'confidential note' },
+ noteableData: { ...notableDataMockCanUpdateIssuable },
+ features,
+ });
+
+ const checkbox = findConfidentialNoteCheckbox();
+ expect(checkbox.exists()).toBe(true);
+ expect(checkbox.element.checked).toBe(false);
+ });
+
+ describe.each`
+ shouldCheckboxBeChecked
+ ${true}
+ ${false}
+ `('when checkbox value is `$shouldCheckboxBeChecked`', ({ shouldCheckboxBeChecked }) => {
+ it(`sets \`confidential\` to \`${shouldCheckboxBeChecked}\``, async () => {
+ mountComponent({
+ mountFunction: mount,
+ initialData: { note: 'confidential note' },
+ noteableData: { ...notableDataMockCanUpdateIssuable },
+ features,
+ });
+
+ jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue({});
+
+ const checkbox = findConfidentialNoteCheckbox();
+
+ // check checkbox
+ checkbox.element.checked = shouldCheckboxBeChecked;
+ checkbox.trigger('change');
+ await wrapper.vm.$nextTick();
+
+ // submit comment
+ wrapper.findByTestId('comment-button').trigger('click');
+
+ const [providedData] = wrapper.vm.saveNote.mock.calls[0];
+ expect(providedData.data.note.confidential).toBe(shouldCheckboxBeChecked);
+ });
+ });
+
+ describe('when user cannot update issuable', () => {
+ it('should not render checkbox', () => {
+ mountComponent({
+ mountFunction: mount,
+ noteableData: { ...notableDataMockCannotUpdateIssuable },
+ features,
+ });
+
+ expect(findConfidentialNoteCheckbox().exists()).toBe(false);
+ });
+ });
+ });
+ });
});
describe('user is not logged in', () => {
diff --git a/spec/frontend/notes/components/diff_discussion_header_spec.js b/spec/frontend/notes/components/diff_discussion_header_spec.js
index 3940439a32b..fdc89522901 100644
--- a/spec/frontend/notes/components/diff_discussion_header_spec.js
+++ b/spec/frontend/notes/components/diff_discussion_header_spec.js
@@ -1,10 +1,10 @@
import { mount } from '@vue/test-utils';
-import createStore from '~/notes/stores';
import diffDiscussionHeader from '~/notes/components/diff_discussion_header.vue';
+import createStore from '~/notes/stores';
-import { discussionMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_discussions';
+import { discussionMock } from '../mock_data';
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
diff --git a/spec/frontend/notes/components/diff_with_note_spec.js b/spec/frontend/notes/components/diff_with_note_spec.js
index 6480af015db..e997fc4da50 100644
--- a/spec/frontend/notes/components/diff_with_note_spec.js
+++ b/spec/frontend/notes/components/diff_with_note_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import DiffWithNote from '~/notes/components/diff_with_note.vue';
import { createStore } from '~/mr_notes/stores';
+import DiffWithNote from '~/notes/components/diff_with_note.vue';
const discussionFixture = 'merge_requests/diff_discussion.json';
const imageDiscussionFixture = 'merge_requests/image_diff_discussion.json';
diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js
index 48e569720e9..03e5842bb0f 100644
--- a/spec/frontend/notes/components/discussion_actions_spec.js
+++ b/spec/frontend/notes/components/discussion_actions_spec.js
@@ -1,10 +1,10 @@
import { shallowMount, mount } from '@vue/test-utils';
-import { discussionMock } from '../mock_data';
import DiscussionActions from '~/notes/components/discussion_actions.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ResolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
import createStore from '~/notes/stores';
+import { discussionMock } from '../mock_data';
// NOTE: clone mock_data so that it is not accidentally mutated
const createDiscussionMock = (props = {}) =>
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index ebf7d52f38b..9db0f823d84 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
-import notesModule from '~/notes/stores/modules';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import DiscussionCounter from '~/notes/components/discussion_counter.vue';
-import { noteableDataMock, discussionMock, notesDataMock, userDataMock } from '../mock_data';
+import notesModule from '~/notes/stores/modules';
import * as types from '~/notes/stores/mutation_types';
+import { noteableDataMock, discussionMock, notesDataMock, userDataMock } from '../mock_data';
describe('DiscussionCounter component', () => {
let store;
diff --git a/spec/frontend/notes/components/discussion_filter_note_spec.js b/spec/frontend/notes/components/discussion_filter_note_spec.js
index 9ae3f08df77..ad9a2e898eb 100644
--- a/spec/frontend/notes/components/discussion_filter_note_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_note_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
import eventHub from '~/notes/event_hub';
diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js
index aeba8e8056c..6f62b8ba528 100644
--- a/spec/frontend/notes/components/discussion_filter_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_spec.js
@@ -1,13 +1,13 @@
-import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import createEventHub from '~/helpers/event_hub_factory';
import axios from '~/lib/utils/axios_utils';
-import notesModule from '~/notes/stores/modules';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
+import notesModule from '~/notes/stores/modules';
import { discussionFiltersMock, discussionMock } from '../mock_data';
diff --git a/spec/frontend/notes/components/discussion_navigator_spec.js b/spec/frontend/notes/components/discussion_navigator_spec.js
index 122814b8e3f..4d55eee2ffa 100644
--- a/spec/frontend/notes/components/discussion_navigator_spec.js
+++ b/spec/frontend/notes/components/discussion_navigator_spec.js
@@ -1,7 +1,7 @@
/* global Mousetrap */
import 'mousetrap';
-import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import DiscussionNavigator from '~/notes/components/discussion_navigator.vue';
import eventHub from '~/notes/event_hub';
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index e803dcb7b4a..cd24b9afbdf 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -1,13 +1,13 @@
-import { shallowMount } from '@vue/test-utils';
import { getByRole } from '@testing-library/dom';
+import { shallowMount } from '@vue/test-utils';
import '~/behaviors/markdown/render_gfm';
-import { SYSTEM_NOTE } from '~/notes/constants';
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
import NoteableNote from '~/notes/components/noteable_note.vue';
+import { SYSTEM_NOTE } from '~/notes/constants';
+import createStore from '~/notes/stores';
import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
import SystemNote from '~/vue_shared/components/notes/system_note.vue';
-import createStore from '~/notes/stores';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
const LINE_RANGE = {};
@@ -23,7 +23,7 @@ describe('DiscussionNotes', () => {
let wrapper;
const getList = () => getByRole(wrapper.element, 'list');
- const createComponent = (props, features = {}) => {
+ const createComponent = (props) => {
wrapper = shallowMount(DiscussionNotes, {
store,
propsData: {
@@ -38,9 +38,6 @@ describe('DiscussionNotes', () => {
slots: {
'avatar-badge': '<span class="avatar-badge-slot-content" />',
},
- provide: {
- glFeatures: { multilineComments: true, ...features },
- },
});
};
@@ -177,16 +174,14 @@ describe('DiscussionNotes', () => {
});
describe.each`
- desc | props | features | event | expectedCalls
- ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{}} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]}
- ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{}} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]}
- ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{ multilineComments: false }} | ${'mouseenter'} | ${[]}
- ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{ multilineComments: false }} | ${'mouseleave'} | ${[]}
- ${'without `discussion.position`'} | ${{}} | ${{}} | ${'mouseenter'} | ${[]}
- ${'without `discussion.position`'} | ${{}} | ${{}} | ${'mouseleave'} | ${[]}
- `('$desc and features $features', ({ props, event, features, expectedCalls }) => {
+ desc | props | event | expectedCalls
+ ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]}
+ ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]}
+ ${'without `discussion.position`'} | ${{}} | ${'mouseenter'} | ${[]}
+ ${'without `discussion.position`'} | ${{}} | ${'mouseleave'} | ${[]}
+ `('$desc', ({ props, event, expectedCalls }) => {
beforeEach(() => {
- createComponent(props, features);
+ createComponent(props);
jest.spyOn(store, 'dispatch');
});
diff --git a/spec/frontend/notes/components/discussion_resolve_button_spec.js b/spec/frontend/notes/components/discussion_resolve_button_spec.js
index 5105e1013d3..64e061830b9 100644
--- a/spec/frontend/notes/components/discussion_resolve_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import resolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue';
const buttonTitle = 'Resolve discussion';
diff --git a/spec/frontend/notes/components/multiline_comment_form_spec.js b/spec/frontend/notes/components/multiline_comment_form_spec.js
index 081fd6e10ef..b6d603c6358 100644
--- a/spec/frontend/notes/components/multiline_comment_form_spec.js
+++ b/spec/frontend/notes/components/multiline_comment_form_spec.js
@@ -1,7 +1,7 @@
+import { GlFormSelect } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { mount } from '@vue/test-utils';
-import { GlFormSelect } from '@gitlab/ui';
import MultilineCommentForm from '~/notes/components/multiline_comment_form.vue';
import notesModule from '~/notes/stores/modules';
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 720ab10b270..4993ded365d 100644
--- a/spec/frontend/notes/components/note_actions/reply_button_spec.js
+++ b/spec/frontend/notes/components/note_actions/reply_button_spec.js
@@ -1,29 +1,22 @@
-import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ReplyButton from '~/notes/components/note_actions/reply_button.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
describe('ReplyButton', () => {
let wrapper;
beforeEach(() => {
- wrapper = mount(localVue.extend(ReplyButton), {
- localVue,
- });
+ wrapper = shallowMount(ReplyButton);
});
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
it('emits startReplying on click', () => {
- const button = wrapper.find({ ref: 'button' });
-
- button.trigger('click');
+ wrapper.find(GlButton).vm.$emit('click');
- expect(wrapper.emitted().startReplying).toBeTruthy();
- expect(wrapper.emitted().startReplying.length).toBe(1);
+ expect(wrapper.emitted('startReplying')).toEqual([[]]);
});
});
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 3cfc1445cb8..17717ebd09a 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -1,11 +1,12 @@
-import Vue from 'vue';
import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
-import { TEST_HOST } from 'spec/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
-import createStore from '~/notes/stores';
+import Vue from 'vue';
+import { TEST_HOST } from 'spec/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import noteActions from '~/notes/components/note_actions.vue';
+import createStore from '~/notes/stores';
import { userDataMock } from '../mock_data';
-import axios from '~/lib/utils/axios_utils';
describe('noteActions', () => {
let wrapper;
@@ -135,7 +136,7 @@ describe('noteActions', () => {
.then(() => {
const emitted = Object.keys(rootWrapper.emitted());
- expect(emitted).toEqual(['bv::hide::tooltip']);
+ expect(emitted).toEqual([BV_HIDE_TOOLTIP]);
done();
})
.catch(done.fail);
diff --git a/spec/frontend/notes/components/note_awards_list_spec.js b/spec/frontend/notes/components/note_awards_list_spec.js
index 13a817902e6..9fc89ffa473 100644
--- a/spec/frontend/notes/components/note_awards_list_spec.js
+++ b/spec/frontend/notes/components/note_awards_list_spec.js
@@ -1,9 +1,9 @@
-import Vue from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import createStore from '~/notes/stores';
import awardsNote from '~/notes/components/note_awards_list.vue';
+import createStore from '~/notes/stores';
import { noteableDataMock, notesDataMock } from '../mock_data';
describe('note_awards_list component', () => {
diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js
index 3c11c266f90..4922de987fa 100644
--- a/spec/frontend/notes/components/note_body_spec.js
+++ b/spec/frontend/notes/components/note_body_spec.js
@@ -1,6 +1,14 @@
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
-import createStore from '~/notes/stores';
+import Vuex from 'vuex';
+
+import { suggestionCommitMessage } from '~/diffs/store/getters';
import noteBody from '~/notes/components/note_body.vue';
+import createStore from '~/notes/stores';
+import notes from '~/notes/stores/modules/index';
+
+import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
+
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note_body component', () => {
@@ -54,4 +62,50 @@ describe('issue_note_body component', () => {
expect(vm.autosave.key).toEqual(autosaveKey);
});
});
+
+ describe('commitMessage', () => {
+ let wrapper;
+
+ Vue.use(Vuex);
+
+ beforeEach(() => {
+ const notesStore = notes();
+
+ notesStore.state.notes = {};
+
+ store = new Vuex.Store({
+ modules: {
+ notes: notesStore,
+ diffs: {
+ namespaced: true,
+ state: {
+ defaultSuggestionCommitMessage:
+ '%{branch_name}%{project_path}%{project_name}%{username}%{user_full_name}%{file_paths}%{suggestions_count}%{files_count}',
+ branchName: 'branch',
+ projectPath: '/path',
+ projectName: 'name',
+ username: 'user',
+ userFullName: 'user userton',
+ },
+ getters: { suggestionCommitMessage },
+ },
+ },
+ });
+
+ wrapper = shallowMount(noteBody, {
+ store,
+ propsData: {
+ note: { ...note, suggestions: [12345] },
+ canEdit: true,
+ file: { file_path: 'abc' },
+ },
+ });
+ });
+
+ it('passes the correct default placeholder commit message for a suggestion to the suggestions component', () => {
+ const commitMessage = wrapper.find(Suggestions).attributes('defaultcommitmessage');
+
+ expect(commitMessage).toBe('branch/pathnameuseruser usertonabc11');
+ });
+ });
});
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index e64a75bede9..7615f3b70f1 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -1,13 +1,12 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import createStore from '~/notes/stores';
-import NoteForm from '~/notes/components/note_form.vue';
import batchComments from '~/batch_comments/stores/modules/batch_comments';
+import { getDraft, updateDraft } from '~/lib/utils/autosave';
+import NoteForm from '~/notes/components/note_form.vue';
+import createStore from '~/notes/stores';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { noteableDataMock, notesDataMock, discussionMock } from '../mock_data';
-import { getDraft, updateDraft } from '~/lib/utils/autosave';
-
jest.mock('~/lib/utils/autosave');
describe('issue_note_form component', () => {
@@ -25,6 +24,8 @@ describe('issue_note_form component', () => {
});
};
+ const findCancelButton = () => wrapper.find('[data-testid="cancel"]');
+
beforeEach(() => {
getDraft.mockImplementation((key) => {
if (key === dummyAutosaveKey) {
@@ -160,8 +161,8 @@ describe('issue_note_form component', () => {
});
await nextTick();
- const cancelButton = wrapper.find('[data-testid="cancel"]');
- cancelButton.trigger('click');
+ const cancelButton = findCancelButton();
+ cancelButton.vm.$emit('click');
await nextTick();
expect(wrapper.emitted().cancelForm).toHaveLength(1);
@@ -177,7 +178,7 @@ describe('issue_note_form component', () => {
const textarea = wrapper.find('textarea');
textarea.setValue('Foo');
const saveButton = wrapper.find('.js-vue-issue-save');
- saveButton.trigger('click');
+ saveButton.vm.$emit('click');
expect(wrapper.vm.isSubmitting).toBe(true);
});
@@ -272,7 +273,7 @@ describe('issue_note_form component', () => {
await nextTick();
const cancelButton = wrapper.find('[data-testid="cancelBatchCommentsEnabled"]');
- cancelButton.trigger('click');
+ cancelButton.vm.$emit('click');
expect(wrapper.vm.cancelHandler).toHaveBeenCalledWith(true);
});
@@ -302,16 +303,16 @@ describe('issue_note_form component', () => {
expect(wrapper.find('.js-resolve-checkbox').exists()).toBe(false);
});
- it('hides actions for commits', () => {
+ it('hides actions for commits', async () => {
wrapper.setProps({ discussion: { for_commit: true } });
- return nextTick(() => {
- expect(wrapper.find('.note-form-actions').text()).not.toContain('Start a review');
- });
+ await nextTick();
+
+ expect(wrapper.find('.note-form-actions').text()).not.toContain('Start a review');
});
describe('on enter', () => {
- it('should start review or add to review when cmd+enter is pressed', () => {
+ it('should start review or add to review when cmd+enter is pressed', async () => {
const textarea = wrapper.find('textarea');
jest.spyOn(wrapper.vm, 'handleAddToReview');
@@ -319,9 +320,8 @@ describe('issue_note_form component', () => {
textarea.setValue('Foo');
textarea.trigger('keydown.enter', { metaKey: true });
- return nextTick(() => {
- expect(wrapper.vm.handleAddToReview).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.vm.handleAddToReview).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 132e3d8aa7e..774d5aaa7d3 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -1,9 +1,10 @@
+import { GlSprintf } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
-import { GlSprintf } from '@gitlab/ui';
import NoteHeader from '~/notes/components/note_header.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -36,9 +37,7 @@ describe('NoteHeader component', () => {
username: 'root',
show_status: true,
status_tooltip_html: statusHtml,
- status: {
- availability: '',
- },
+ availability: '',
};
const createComponent = (props) => {
@@ -48,7 +47,7 @@ describe('NoteHeader component', () => {
actions,
}),
propsData: { ...props },
- stubs: { GlSprintf },
+ stubs: { GlSprintf, UserNameWithStatus },
});
};
@@ -110,7 +109,7 @@ describe('NoteHeader component', () => {
});
it('renders busy status if author availability is set', () => {
- createComponent({ author: { ...author, status: { availability: AVAILABILITY_STATUS.BUSY } } });
+ createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } });
expect(wrapper.find('.js-user-link').text()).toContain('(Busy)');
});
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index b87c6cd7f2b..87538279c3d 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -1,13 +1,13 @@
-import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
-import mockDiffFile from 'jest/diffs/mock_data/diff_file';
+import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
-import createStore from '~/notes/stores';
-import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
+import mockDiffFile from 'jest/diffs/mock_data/diff_file';
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
import NoteForm from '~/notes/components/note_form.vue';
+import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
+import createStore from '~/notes/stores';
import '~/behaviors/markdown/render_gfm';
import {
noteableDataMock,
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index 6f06665f412..fe78e086403 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -1,22 +1,13 @@
-import { escape } from 'lodash';
import { mount, createLocalVue } from '@vue/test-utils';
-import createStore from '~/notes/stores';
-import issueNote from '~/notes/components/noteable_note.vue';
-import NoteHeader from '~/notes/components/note_header.vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import { escape } from 'lodash';
import NoteActions from '~/notes/components/note_actions.vue';
import NoteBody from '~/notes/components/note_body.vue';
+import NoteHeader from '~/notes/components/note_header.vue';
+import issueNote from '~/notes/components/noteable_note.vue';
+import createStore from '~/notes/stores';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
-jest.mock('~/vue_shared/mixins/gl_feature_flags_mixin', () => () => ({
- inject: {
- glFeatures: {
- from: 'glFeatures',
- default: () => ({ multilineComments: true }),
- },
- },
-}));
-
describe('issue_note', () => {
let store;
let wrapper;
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index e495a4738e0..efee72dea96 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -1,18 +1,18 @@
-import $ from 'jquery';
+import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import Vue from 'vue';
-import { mount, shallowMount } from '@vue/test-utils';
import { setTestTimeout } from 'helpers/timeout';
import axios from '~/lib/utils/axios_utils';
-import NotesApp from '~/notes/components/notes_app.vue';
+import * as urlUtility from '~/lib/utils/url_utility';
import CommentForm from '~/notes/components/comment_form.vue';
-import createStore from '~/notes/stores';
+import NotesApp from '~/notes/components/notes_app.vue';
import * as constants from '~/notes/constants';
+import createStore from '~/notes/stores';
import '~/behaviors/markdown/render_gfm';
// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-foss/issues/62491)
-import * as mockData from '../mock_data';
-import * as urlUtility from '~/lib/utils/url_utility';
import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
+import * as mockData from '../mock_data';
jest.mock('~/user_popovers', () => jest.fn());
diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js
index 739e247735d..60f03a0f5b5 100644
--- a/spec/frontend/notes/components/sort_discussion_spec.js
+++ b/spec/frontend/notes/components/sort_discussion_spec.js
@@ -1,10 +1,10 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import SortDiscussion from '~/notes/components/sort_discussion.vue';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-import createStore from '~/notes/stores';
import { ASC, DESC } from '~/notes/constants';
+import createStore from '~/notes/stores';
import Tracking from '~/tracking';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/notes/components/timeline_toggle_spec.js b/spec/frontend/notes/components/timeline_toggle_spec.js
index b8df6fc7996..73fb2079e31 100644
--- a/spec/frontend/notes/components/timeline_toggle_spec.js
+++ b/spec/frontend/notes/components/timeline_toggle_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import TimelineToggle, {
timelineEnabledTooltip,
timelineDisabledTooltip,
} from '~/notes/components/timeline_toggle.vue';
-import createStore from '~/notes/stores';
import { ASC, DESC } from '~/notes/constants';
+import createStore from '~/notes/stores';
import { trackToggleTimelineView } from '~/notes/utils';
import Tracking from '~/tracking';
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index 9c9a648d213..6a6e47ffcc5 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { setHTMLFixture } from 'helpers/fixtures';
+import createEventHub from '~/helpers/event_hub_factory';
import * as utils from '~/lib/utils/common_utils';
-import discussionNavigation from '~/notes/mixins/discussion_navigation';
import eventHub from '~/notes/event_hub';
-import createEventHub from '~/helpers/event_hub_factory';
+import discussionNavigation from '~/notes/mixins/discussion_navigation';
import notesModule from '~/notes/stores/modules';
const discussion = (id, index) => ({
diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js
index 00821980e8a..432b660c4b3 100644
--- a/spec/frontend/notes/old_notes_spec.js
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -1,13 +1,13 @@
/* eslint-disable import/no-commonjs, no-new */
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { createSpyObj } from 'helpers/jest_helpers';
-import { setTestTimeoutOnce } from 'helpers/timeout';
import { TEST_HOST } from 'helpers/test_constants';
-import * as urlUtility from '~/lib/utils/url_utility';
+import { setTestTimeoutOnce } from 'helpers/timeout';
import axios from '~/lib/utils/axios_utils';
+import * as urlUtility from '~/lib/utils/url_utility';
// These must be imported synchronously because they pull dependencies
// from the DOM.
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index f0e6a0a68dd..1852108b39f 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -1,13 +1,18 @@
-import { TEST_HOST } from 'spec/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
import { deprecatedCreateFlash as Flash } from '~/flash';
-import * as actions from '~/notes/stores/actions';
-import mutations from '~/notes/stores/mutations';
-import * as mutationTypes from '~/notes/stores/mutation_types';
+import axios from '~/lib/utils/axios_utils';
import * as notesConstants from '~/notes/constants';
import createStore from '~/notes/stores';
+import * as actions from '~/notes/stores/actions';
+import * as mutationTypes from '~/notes/stores/mutation_types';
+import mutations from '~/notes/stores/mutations';
+import * as utils from '~/notes/stores/utils';
+import updateIssueConfidentialMutation from '~/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql';
+import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql';
+import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
import { resetStore } from '../helpers';
import {
@@ -18,11 +23,6 @@ import {
individualNote,
batchSuggestionsInfoMock,
} from '../mock_data';
-import axios from '~/lib/utils/axios_utils';
-import * as utils from '~/notes/stores/utils';
-import updateIssueConfidentialMutation from '~/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql';
-import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql';
-import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql';
const TEST_ERROR_MESSAGE = 'Test error message';
jest.mock('~/flash');
@@ -291,9 +291,45 @@ describe('Actions Notes Store', () => {
[
{ type: 'updateOrCreateNotes', payload: discussionMock.notes },
{ type: 'startTaskList' },
+ { type: 'updateResolvableDiscussionsCounts' },
],
));
});
+
+ describe('paginated notes feature flag enabled', () => {
+ const lastFetchedAt = '12358';
+
+ beforeEach(() => {
+ window.gon = { features: { paginatedNotes: true } };
+
+ axiosMock.onGet(notesDataMock.notesPath).replyOnce(200, {
+ notes: discussionMock.notes,
+ more: false,
+ last_fetched_at: lastFetchedAt,
+ });
+ });
+
+ afterEach(() => {
+ window.gon = null;
+ });
+
+ it('should dispatch setFetchingState, setNotesFetchedState, setLoadingState, updateOrCreateNotes, startTaskList and commit SET_LAST_FETCHED_AT', () => {
+ return testAction(
+ actions.fetchData,
+ null,
+ { notesData: notesDataMock, isFetching: true },
+ [{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
+ [
+ { type: 'setFetchingState', payload: false },
+ { type: 'setNotesFetchedState', payload: true },
+ { type: 'setLoadingState', payload: false },
+ { type: 'updateOrCreateNotes', payload: discussionMock.notes },
+ { type: 'startTaskList' },
+ { type: 'updateResolvableDiscussionsCounts' },
+ ],
+ );
+ });
+ });
});
describe('poll', () => {
@@ -1276,6 +1312,7 @@ describe('Actions Notes Store', () => {
return actions
.updateConfidentialityOnIssuable({ commit: commitSpy, state, getters }, actionArgs)
.then(() => {
+ expect(Flash).not.toHaveBeenCalled();
expect(commitSpy).toHaveBeenCalledWith(
mutationTypes.SET_ISSUE_CONFIDENTIAL,
confidential,
@@ -1283,6 +1320,22 @@ describe('Actions Notes Store', () => {
});
});
});
+
+ describe('on user recoverable error', () => {
+ it('sends the error to Flash', () => {
+ const error = 'error';
+
+ jest
+ .spyOn(utils.gqClient, 'mutate')
+ .mockResolvedValue({ data: { issueSetConfidential: { errors: [error] } } });
+
+ return actions
+ .updateConfidentialityOnIssuable({ commit: () => {}, state, getters }, actionArgs)
+ .then(() => {
+ expect(Flash).toHaveBeenCalledWith(error, 'alert');
+ });
+ });
+ });
});
describe.each`
@@ -1355,4 +1408,17 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('setFetchingState', () => {
+ it('commits SET_NOTES_FETCHING_STATE', (done) => {
+ testAction(
+ actions.setFetchingState,
+ true,
+ null,
+ [{ type: mutationTypes.SET_NOTES_FETCHING_STATE, payload: true }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js
index fd04d08b6a5..4ebfc679310 100644
--- a/spec/frontend/notes/stores/getters_spec.js
+++ b/spec/frontend/notes/stores/getters_spec.js
@@ -1,5 +1,5 @@
-import * as getters from '~/notes/stores/getters';
import { DESC } from '~/notes/constants';
+import * as getters from '~/notes/stores/getters';
import {
notesDataMock,
userDataMock,
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index 66fc74525ad..99e24f724f4 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import mutations from '~/notes/stores/mutations';
import { DISCUSSION_NOTE, ASC, DESC } from '~/notes/constants';
+import mutations from '~/notes/stores/mutations';
import {
note,
discussionMock,
@@ -400,6 +400,19 @@ describe('Notes Store mutations', () => {
expect(state.discussions[0].notes[0].note).toEqual('Foo');
});
+ it('does not update existing note if it matches', () => {
+ const state = {
+ discussions: [{ ...individualNote, individual_note: false }],
+ };
+ jest.spyOn(state.discussions[0].notes, 'splice');
+
+ const updated = individualNote.notes[0];
+
+ mutations.UPDATE_NOTE(state, updated);
+
+ expect(state.discussions[0].notes.splice).not.toHaveBeenCalled();
+ });
+
it('transforms an individual note to discussion', () => {
const state = {
discussions: [individualNote],
diff --git a/spec/frontend/notifications/components/custom_notifications_modal_spec.js b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
new file mode 100644
index 00000000000..3e87f3107bd
--- /dev/null
+++ b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
@@ -0,0 +1,267 @@
+import { GlSprintf, GlModal, GlFormGroup, GlFormCheckbox, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import httpStatus from '~/lib/utils/http_status';
+import CustomNotificationsModal from '~/notifications/components/custom_notifications_modal.vue';
+import { i18n } from '~/notifications/constants';
+
+const mockNotificationSettingsResponses = {
+ default: {
+ level: 'custom',
+ events: {
+ new_release: true,
+ new_note: false,
+ },
+ },
+ updated: {
+ level: 'custom',
+ events: {
+ new_release: true,
+ new_note: true,
+ },
+ },
+};
+
+const mockToastShow = jest.fn();
+
+describe('CustomNotificationsModal', () => {
+ let wrapper;
+ let mockAxios;
+
+ function createComponent(options = {}) {
+ const { injectedProperties = {}, props = {} } = options;
+ return extendedWrapper(
+ shallowMount(CustomNotificationsModal, {
+ props: {
+ ...props,
+ },
+ provide: {
+ ...injectedProperties,
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
+ },
+ stubs: {
+ GlModal,
+ GlFormGroup,
+ GlFormCheckbox,
+ },
+ }),
+ );
+ }
+
+ const findModalBodyDescription = () => wrapper.find(GlSprintf);
+ const findAllCheckboxes = () => wrapper.findAll(GlFormCheckbox);
+ const findCheckboxAt = (index) => findAllCheckboxes().at(index);
+
+ beforeEach(() => {
+ gon.api_version = 'v4';
+ mockAxios = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mockAxios.restore();
+ });
+
+ describe('template', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('displays the body title and the body message', () => {
+ expect(wrapper.findByTestId('modalBodyTitle').text()).toBe(
+ i18n.customNotificationsModal.bodyTitle,
+ );
+ expect(findModalBodyDescription().attributes('message')).toContain(
+ i18n.customNotificationsModal.bodyMessage,
+ );
+ });
+
+ describe('checkbox items', () => {
+ beforeEach(async () => {
+ wrapper = createComponent();
+
+ wrapper.setData({
+ events: [
+ { id: 'new_release', enabled: true, name: 'New release', loading: false },
+ { id: 'new_note', enabled: false, name: 'New note', loading: true },
+ ],
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it.each`
+ index | eventId | eventName | enabled | loading
+ ${0} | ${'new_release'} | ${'New release'} | ${true} | ${false}
+ ${1} | ${'new_note'} | ${'New note'} | ${false} | ${true}
+ `(
+ 'renders a checkbox for "$eventName" with checked=$enabled',
+ async ({ index, eventName, enabled, loading }) => {
+ const checkbox = findCheckboxAt(index);
+ expect(checkbox.text()).toContain(eventName);
+ expect(checkbox.vm.$attrs.checked).toBe(enabled);
+ expect(checkbox.find(GlLoadingIcon).exists()).toBe(loading);
+ },
+ );
+ });
+ });
+
+ describe('API calls', () => {
+ describe('load notification settings', () => {
+ beforeEach(() => {
+ jest.spyOn(axios, 'get');
+ });
+
+ it.each`
+ projectId | groupId | endpointUrl | notificationType | condition
+ ${1} | ${null} | ${'/api/v4/projects/1/notification_settings'} | ${'project'} | ${'a projectId is given'}
+ ${null} | ${1} | ${'/api/v4/groups/1/notification_settings'} | ${'group'} | ${'a groupId is given'}
+ ${null} | ${null} | ${'/api/v4/notification_settings'} | ${'global'} | ${'neither projectId nor groupId are given'}
+ `(
+ 'requests $notificationType notification settings when $condition',
+ async ({ projectId, groupId, endpointUrl }) => {
+ const injectedProperties = {
+ projectId,
+ groupId,
+ };
+
+ mockAxios
+ .onGet(endpointUrl)
+ .reply(httpStatus.OK, mockNotificationSettingsResponses.default);
+
+ wrapper = createComponent({ injectedProperties });
+
+ wrapper.find(GlModal).vm.$emit('show');
+
+ await waitForPromises();
+
+ expect(axios.get).toHaveBeenCalledWith(endpointUrl);
+ },
+ );
+
+ it('updates the loading state and the events property', async () => {
+ const endpointUrl = '/api/v4/notification_settings';
+
+ mockAxios
+ .onGet(endpointUrl)
+ .reply(httpStatus.OK, mockNotificationSettingsResponses.default);
+
+ wrapper = createComponent();
+
+ wrapper.find(GlModal).vm.$emit('show');
+ expect(wrapper.vm.isLoading).toBe(true);
+
+ await waitForPromises();
+
+ expect(axios.get).toHaveBeenCalledWith(endpointUrl);
+ expect(wrapper.vm.isLoading).toBe(false);
+ expect(wrapper.vm.events).toEqual([
+ { id: 'new_release', enabled: true, name: 'New release', loading: false },
+ { id: 'new_note', enabled: false, name: 'New note', loading: false },
+ ]);
+ });
+
+ it('shows a toast message when the request fails', async () => {
+ mockAxios.onGet('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
+ wrapper = createComponent();
+
+ wrapper.find(GlModal).vm.$emit('show');
+
+ await waitForPromises();
+
+ expect(
+ mockToastShow,
+ ).toHaveBeenCalledWith(
+ 'An error occured while loading the notification settings. Please try again.',
+ { type: 'error' },
+ );
+ });
+ });
+
+ describe('update notification settings', () => {
+ beforeEach(() => {
+ jest.spyOn(axios, 'put');
+ });
+
+ it.each`
+ projectId | groupId | endpointUrl | notificationType | condition
+ ${1} | ${null} | ${'/api/v4/projects/1/notification_settings'} | ${'project'} | ${'a projectId is given'}
+ ${null} | ${1} | ${'/api/v4/groups/1/notification_settings'} | ${'group'} | ${'a groupId is given'}
+ ${null} | ${null} | ${'/api/v4/notification_settings'} | ${'global'} | ${'neither projectId nor groupId are given'}
+ `(
+ 'updates the $notificationType notification settings when $condition and the user clicks the checkbox ',
+ async ({ projectId, groupId, endpointUrl }) => {
+ mockAxios
+ .onGet(endpointUrl)
+ .reply(httpStatus.OK, mockNotificationSettingsResponses.default);
+
+ mockAxios
+ .onPut(endpointUrl)
+ .reply(httpStatus.OK, mockNotificationSettingsResponses.updated);
+
+ const injectedProperties = {
+ projectId,
+ groupId,
+ };
+
+ wrapper = createComponent({ injectedProperties });
+
+ wrapper.setData({
+ events: [
+ { id: 'new_release', enabled: true, name: 'New release', loading: false },
+ { id: 'new_note', enabled: false, name: 'New note', loading: false },
+ ],
+ });
+
+ await wrapper.vm.$nextTick();
+
+ findCheckboxAt(1).vm.$emit('change', true);
+
+ await waitForPromises();
+
+ expect(axios.put).toHaveBeenCalledWith(endpointUrl, {
+ new_note: true,
+ });
+
+ expect(wrapper.vm.events).toEqual([
+ { id: 'new_release', enabled: true, name: 'New release', loading: false },
+ { id: 'new_note', enabled: true, name: 'New note', loading: false },
+ ]);
+ },
+ );
+
+ it('shows a toast message when the request fails', async () => {
+ mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
+ wrapper = createComponent();
+
+ wrapper.setData({
+ events: [
+ { id: 'new_release', enabled: true, name: 'New release', loading: false },
+ { id: 'new_note', enabled: false, name: 'New note', loading: false },
+ ],
+ });
+
+ await wrapper.vm.$nextTick();
+
+ findCheckboxAt(1).vm.$emit('change', true);
+
+ await waitForPromises();
+
+ expect(
+ mockToastShow,
+ ).toHaveBeenCalledWith(
+ 'An error occured while updating the notification settings. Please try again.',
+ { type: 'error' },
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/notifications/components/notifications_dropdown_spec.js b/spec/frontend/notifications/components/notifications_dropdown_spec.js
new file mode 100644
index 00000000000..0673fb51a91
--- /dev/null
+++ b/spec/frontend/notifications/components/notifications_dropdown_spec.js
@@ -0,0 +1,274 @@
+import { GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import waitForPromises from 'helpers/wait_for_promises';
+import httpStatus from '~/lib/utils/http_status';
+import CustomNotificationsModal from '~/notifications/components/custom_notifications_modal.vue';
+import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
+import NotificationsDropdownItem from '~/notifications/components/notifications_dropdown_item.vue';
+
+const mockDropdownItems = ['global', 'watch', 'participating', 'mention', 'disabled'];
+const mockToastShow = jest.fn();
+
+describe('NotificationsDropdown', () => {
+ let wrapper;
+ let mockAxios;
+ let glModalDirective;
+
+ function createComponent(injectedProperties = {}) {
+ glModalDirective = jest.fn();
+
+ return shallowMount(NotificationsDropdown, {
+ stubs: {
+ GlButtonGroup,
+ GlDropdown,
+ GlDropdownItem,
+ NotificationsDropdownItem,
+ CustomNotificationsModal,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ glModal: {
+ bind(_, { value }) {
+ glModalDirective(value);
+ },
+ },
+ },
+ provide: {
+ dropdownItems: mockDropdownItems,
+ initialNotificationLevel: 'global',
+ ...injectedProperties,
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
+ },
+ });
+ }
+
+ const findButtonGroup = () => wrapper.find(GlButtonGroup);
+ const findButton = () => wrapper.find(GlButton);
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
+ const findAllNotificationsDropdownItems = () => wrapper.findAll(NotificationsDropdownItem);
+ const findDropdownItemAt = (index) =>
+ findAllNotificationsDropdownItems().at(index).find(GlDropdownItem);
+
+ const clickDropdownItemAt = async (index) => {
+ const dropdownItem = findDropdownItemAt(index);
+ dropdownItem.vm.$emit('click');
+
+ await waitForPromises();
+ };
+
+ beforeEach(() => {
+ gon.api_version = 'v4';
+ mockAxios = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mockAxios.restore();
+ });
+
+ describe('template', () => {
+ describe('when notification level is "custom"', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ initialNotificationLevel: 'custom',
+ });
+ });
+
+ it('renders a button group', () => {
+ expect(findButtonGroup().exists()).toBe(true);
+ });
+
+ it('shows the button text when showLabel is true', () => {
+ wrapper = createComponent({
+ initialNotificationLevel: 'custom',
+ showLabel: true,
+ });
+
+ expect(findButton().text()).toBe('Custom');
+ });
+
+ it("doesn't show the button text when showLabel is false", () => {
+ wrapper = createComponent({
+ initialNotificationLevel: 'custom',
+ showLabel: false,
+ });
+
+ expect(findButton().text()).toBe('');
+ });
+
+ it('opens the modal when the user clicks the button', async () => {
+ jest.spyOn(axios, 'put');
+ mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.OK, {});
+
+ wrapper = createComponent({
+ initialNotificationLevel: 'custom',
+ });
+
+ findButton().vm.$emit('click');
+
+ expect(glModalDirective).toHaveBeenCalled();
+ });
+ });
+
+ describe('when notification level is not "custom"', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ initialNotificationLevel: 'global',
+ });
+ });
+
+ it('does not render a button group', () => {
+ expect(findButtonGroup().exists()).toBe(false);
+ });
+
+ it('shows the button text when showLabel is true', () => {
+ wrapper = createComponent({
+ showLabel: true,
+ });
+
+ expect(findDropdown().props('text')).toBe('Global');
+ });
+
+ it("doesn't show the button text when showLabel is false", () => {
+ wrapper = createComponent({
+ showLabel: false,
+ });
+
+ expect(findDropdown().props('text')).toBe(null);
+ });
+ });
+
+ describe('button tooltip', () => {
+ const tooltipTitlePrefix = 'Notification setting';
+ it.each`
+ level | title
+ ${'global'} | ${'Global'}
+ ${'watch'} | ${'Watch'}
+ ${'participating'} | ${'Participate'}
+ ${'mention'} | ${'On mention'}
+ ${'disabled'} | ${'Disabled'}
+ ${'custom'} | ${'Custom'}
+ `(`renders "${tooltipTitlePrefix} - $title" for "$level" level`, ({ level, title }) => {
+ wrapper = createComponent({
+ initialNotificationLevel: level,
+ });
+
+ const tooltipElement = findByTestId('notificationButton');
+ const tooltip = getBinding(tooltipElement.element, 'gl-tooltip');
+
+ expect(tooltip.value.title).toBe(`${tooltipTitlePrefix} - ${title}`);
+ });
+ });
+
+ describe('button icon', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ initialNotificationLevel: 'disabled',
+ });
+ });
+
+ it('renders the "notifications-off" icon when notification level is "disabled"', () => {
+ expect(findDropdown().props('icon')).toBe('notifications-off');
+ });
+
+ it('renders the "notifications" icon when notification level is not "disabled"', () => {
+ wrapper = createComponent();
+
+ expect(findDropdown().props('icon')).toBe('notifications');
+ });
+ });
+
+ describe('dropdown items', () => {
+ it.each`
+ dropdownIndex | level | title | description
+ ${0} | ${'global'} | ${'Global'} | ${'Use your global notification setting'}
+ ${1} | ${'watch'} | ${'Watch'} | ${'You will receive notifications for any activity'}
+ ${2} | ${'participating'} | ${'Participate'} | ${'You will only receive notifications for threads you have participated in'}
+ ${3} | ${'mention'} | ${'On mention'} | ${'You will receive notifications only for comments in which you were @mentioned'}
+ ${4} | ${'disabled'} | ${'Disabled'} | ${'You will not get any notifications via email'}
+ ${5} | ${'custom'} | ${'Custom'} | ${'You will only receive notifications for the events you choose'}
+ `('displays "$title" and "$description"', ({ dropdownIndex, title, description }) => {
+ wrapper = createComponent();
+
+ expect(findAllNotificationsDropdownItems().at(dropdownIndex).props('title')).toBe(title);
+ expect(findAllNotificationsDropdownItems().at(dropdownIndex).props('description')).toBe(
+ description,
+ );
+ });
+ });
+ });
+
+ describe('when selecting an item', () => {
+ beforeEach(() => {
+ jest.spyOn(axios, 'put');
+ });
+
+ it.each`
+ projectId | groupId | endpointUrl | endpointType | condition
+ ${1} | ${null} | ${'/api/v4/projects/1/notification_settings'} | ${'project notifications'} | ${'a projectId is given'}
+ ${null} | ${1} | ${'/api/v4/groups/1/notification_settings'} | ${'group notifications'} | ${'a groupId is given'}
+ ${null} | ${null} | ${'/api/v4/notification_settings'} | ${'global notifications'} | ${'when neither projectId nor groupId are given'}
+ `(
+ 'calls the $endpointType endpoint when $condition',
+ async ({ projectId, groupId, endpointUrl }) => {
+ wrapper = createComponent({
+ projectId,
+ groupId,
+ });
+
+ await clickDropdownItemAt(1);
+
+ expect(axios.put).toHaveBeenCalledWith(endpointUrl, {
+ level: 'watch',
+ });
+ },
+ );
+
+ it('updates the selectedNotificationLevel and marks the item with a checkmark', async () => {
+ mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.OK, {});
+ wrapper = createComponent();
+
+ const dropdownItem = findDropdownItemAt(1);
+
+ await clickDropdownItemAt(1);
+
+ expect(wrapper.vm.selectedNotificationLevel).toBe('watch');
+ expect(dropdownItem.props('isChecked')).toBe(true);
+ });
+
+ it("won't update the selectedNotificationLevel and shows a toast message when the request fails and ", async () => {
+ mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
+ wrapper = createComponent();
+
+ await clickDropdownItemAt(1);
+
+ expect(wrapper.vm.selectedNotificationLevel).toBe('global');
+ expect(
+ mockToastShow,
+ ).toHaveBeenCalledWith(
+ 'An error occured while updating the notification settings. Please try again.',
+ { type: 'error' },
+ );
+ });
+
+ it('opens the modal when the user clicks on the "Custom" dropdown item', async () => {
+ mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.OK, {});
+ wrapper = createComponent();
+
+ const mockModalShow = jest.spyOn(wrapper.vm.$refs.customNotificationsModal, 'open');
+
+ await clickDropdownItemAt(5);
+
+ expect(mockModalShow).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/onboarding_issues/index_spec.js b/spec/frontend/onboarding_issues/index_spec.js
deleted file mode 100644
index d476ba1cf5a..00000000000
--- a/spec/frontend/onboarding_issues/index_spec.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import $ from 'jquery';
-import setWindowLocation from 'helpers/set_window_location_helper';
-import { showLearnGitLabIssuesPopover } from '~/onboarding_issues';
-import { getCookie, setCookie, removeCookie } from '~/lib/utils/common_utils';
-import Tracking from '~/tracking';
-
-describe('Onboarding Issues Popovers', () => {
- const COOKIE_NAME = 'onboarding_issues_settings';
- const getCookieValue = () => JSON.parse(getCookie(COOKIE_NAME));
-
- beforeEach(() => {
- jest.spyOn($.fn, 'popover');
- });
-
- afterEach(() => {
- $.fn.popover.mockRestore();
- document.getElementsByTagName('html')[0].innerHTML = '';
- removeCookie(COOKIE_NAME);
- });
-
- const setupShowLearnGitLabIssuesPopoverTest = ({
- currentPath = 'group/learn-gitlab',
- isIssuesBoardsLinkShown = true,
- isCookieSet = true,
- cookieValue = true,
- } = {}) => {
- setWindowLocation(`http://example.com/${currentPath}`);
-
- if (isIssuesBoardsLinkShown) {
- const elem = document.createElement('a');
- elem.setAttribute('data-qa-selector', 'issue_boards_link');
- document.body.appendChild(elem);
- }
-
- if (isCookieSet) {
- setCookie(COOKIE_NAME, { previous: true, 'issues#index': cookieValue });
- }
-
- showLearnGitLabIssuesPopover();
- };
-
- describe('showLearnGitLabIssuesPopover', () => {
- describe('when on another project', () => {
- beforeEach(() => {
- setupShowLearnGitLabIssuesPopoverTest({
- currentPath: 'group/another-project',
- });
- });
-
- it('does not show a popover', () => {
- expect($.fn.popover).not.toHaveBeenCalled();
- });
- });
-
- describe('when the issues boards link is not shown', () => {
- beforeEach(() => {
- setupShowLearnGitLabIssuesPopoverTest({
- isIssuesBoardsLinkShown: false,
- });
- });
-
- it('does not show a popover', () => {
- expect($.fn.popover).not.toHaveBeenCalled();
- });
- });
-
- describe('when the cookie is not set', () => {
- beforeEach(() => {
- setupShowLearnGitLabIssuesPopoverTest({
- isCookieSet: false,
- });
- });
-
- it('does not show a popover', () => {
- expect($.fn.popover).not.toHaveBeenCalled();
- });
- });
-
- describe('when the cookie value is false', () => {
- beforeEach(() => {
- setupShowLearnGitLabIssuesPopoverTest({
- cookieValue: false,
- });
- });
-
- it('does not show a popover', () => {
- expect($.fn.popover).not.toHaveBeenCalled();
- });
- });
-
- describe('with all the right conditions', () => {
- beforeEach(() => {
- setupShowLearnGitLabIssuesPopoverTest();
- });
-
- it('shows a popover', () => {
- expect($.fn.popover).toHaveBeenCalled();
- });
-
- it('does not change the cookie value', () => {
- expect(getCookieValue()['issues#index']).toBe(true);
- });
-
- it('disables the previous popover', () => {
- expect(getCookieValue().previous).toBe(false);
- });
-
- describe('when clicking the issues boards link', () => {
- beforeEach(() => {
- document.querySelector('a[data-qa-selector="issue_boards_link"]').click();
- });
-
- it('deletes the cookie', () => {
- expect(getCookie(COOKIE_NAME)).toBe(undefined);
- });
- });
-
- describe('when dismissing the popover', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- document.querySelector('.learn-gitlab.popover .close').click();
- });
-
- it('deletes the cookie', () => {
- expect(getCookie(COOKIE_NAME)).toBe(undefined);
- });
-
- it('sends a tracking event', () => {
- expect(Tracking.event).toHaveBeenCalledWith(
- 'Growth::Conversion::Experiment::OnboardingIssues',
- 'dismiss_popover',
- );
- });
- });
- });
- });
-});
diff --git a/spec/frontend/operation_settings/components/metrics_settings_spec.js b/spec/frontend/operation_settings/components/metrics_settings_spec.js
index 3216eece391..272e9b71f67 100644
--- a/spec/frontend/operation_settings/components/metrics_settings_spec.js
+++ b/spec/frontend/operation_settings/components/metrics_settings_spec.js
@@ -1,15 +1,15 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlButton, GlLink, GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import { timezones } from '~/monitoring/format_date';
+import DashboardTimezone from '~/operation_settings/components/form_group/dashboard_timezone.vue';
+import ExternalDashboard from '~/operation_settings/components/form_group/external_dashboard.vue';
import MetricsSettings from '~/operation_settings/components/metrics_settings.vue';
-import ExternalDashboard from '~/operation_settings/components/form_group/external_dashboard.vue';
-import DashboardTimezone from '~/operation_settings/components/form_group/dashboard_timezone.vue';
-import { timezones } from '~/monitoring/format_date';
import store from '~/operation_settings/store';
-import axios from '~/lib/utils/axios_utils';
-import { refreshCurrentPage } from '~/lib/utils/url_utility';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash');
diff --git a/spec/frontend/operation_settings/store/mutations_spec.js b/spec/frontend/operation_settings/store/mutations_spec.js
index 88eb66095ad..db6b54b503d 100644
--- a/spec/frontend/operation_settings/store/mutations_spec.js
+++ b/spec/frontend/operation_settings/store/mutations_spec.js
@@ -1,6 +1,6 @@
+import { timezones } from '~/monitoring/format_date';
import mutations from '~/operation_settings/store/mutations';
import createState from '~/operation_settings/store/state';
-import { timezones } from '~/monitoring/format_date';
describe('operation settings mutations', () => {
let localState;
diff --git a/spec/frontend/packages/details/components/additional_metadata_spec.js b/spec/frontend/packages/details/components/additional_metadata_spec.js
index 8466a630ecb..b339aa84348 100644
--- a/spec/frontend/packages/details/components/additional_metadata_spec.js
+++ b/spec/frontend/packages/details/components/additional_metadata_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
-import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+import { shallowMount } from '@vue/test-utils';
import component from '~/packages/details/components/additional_metadata.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
import { mavenPackage, conanPackage, nugetPackage, npmPackage } from '../../mock_data';
diff --git a/spec/frontend/packages/details/components/app_spec.js b/spec/frontend/packages/details/components/app_spec.js
index 97df117df0b..11dad7ba34d 100644
--- a/spec/frontend/packages/details/components/app_spec.js
+++ b/spec/frontend/packages/details/components/app_spec.js
@@ -1,22 +1,21 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlEmptyState, GlModal } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
-import Tracking from '~/tracking';
-import * as getters from '~/packages/details/store/getters';
-import PackagesApp from '~/packages/details/components/app.vue';
-import PackageTitle from '~/packages/details/components/package_title.vue';
-
-import * as SharedUtils from '~/packages/shared/utils';
-import { TrackingActions } from '~/packages/shared/constants';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
-import PackageListRow from '~/packages/shared/components/package_list_row.vue';
-import DependencyRow from '~/packages/details/components/dependency_row.vue';
-import PackageHistory from '~/packages/details/components/package_history.vue';
import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
+import PackagesApp from '~/packages/details/components/app.vue';
+import DependencyRow from '~/packages/details/components/dependency_row.vue';
import InstallationCommands from '~/packages/details/components/installation_commands.vue';
import PackageFiles from '~/packages/details/components/package_files.vue';
+import PackageHistory from '~/packages/details/components/package_history.vue';
+import PackageTitle from '~/packages/details/components/package_title.vue';
+import * as getters from '~/packages/details/store/getters';
+import PackageListRow from '~/packages/shared/components/package_list_row.vue';
+import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
+import { TrackingActions } from '~/packages/shared/constants';
+import * as SharedUtils from '~/packages/shared/utils';
+import Tracking from '~/tracking';
import {
composerPackage,
diff --git a/spec/frontend/packages/details/components/composer_installation_spec.js b/spec/frontend/packages/details/components/composer_installation_spec.js
index b44609e8ae7..a1d30d0ed22 100644
--- a/spec/frontend/packages/details/components/composer_installation_spec.js
+++ b/spec/frontend/packages/details/components/composer_installation_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlSprintf, GlLink } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { registryUrl as composerHelpPath } from 'jest/packages/details/mock_data';
import { composerPackage as packageEntity } from 'jest/packages/mock_data';
import ComposerInstallation from '~/packages/details/components/composer_installation.vue';
diff --git a/spec/frontend/packages/details/components/conan_installation_spec.js b/spec/frontend/packages/details/components/conan_installation_spec.js
index 92c1f8e7f4a..bf8a92a6350 100644
--- a/spec/frontend/packages/details/components/conan_installation_spec.js
+++ b/spec/frontend/packages/details/components/conan_installation_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import ConanInstallation from '~/packages/details/components/conan_installation.vue';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
import { conanPackage as packageEntity } from '../../mock_data';
diff --git a/spec/frontend/packages/details/components/installations_commands_spec.js b/spec/frontend/packages/details/components/installations_commands_spec.js
index 60da34ebcd9..065bf503585 100644
--- a/spec/frontend/packages/details/components/installations_commands_spec.js
+++ b/spec/frontend/packages/details/components/installations_commands_spec.js
@@ -1,12 +1,12 @@
import { shallowMount } from '@vue/test-utils';
+import ComposerInstallation from '~/packages/details/components/composer_installation.vue';
+import ConanInstallation from '~/packages/details/components/conan_installation.vue';
import InstallationCommands from '~/packages/details/components/installation_commands.vue';
-import NpmInstallation from '~/packages/details/components/npm_installation.vue';
import MavenInstallation from '~/packages/details/components/maven_installation.vue';
-import ConanInstallation from '~/packages/details/components/conan_installation.vue';
+import NpmInstallation from '~/packages/details/components/npm_installation.vue';
import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
-import ComposerInstallation from '~/packages/details/components/composer_installation.vue';
import {
conanPackage,
diff --git a/spec/frontend/packages/details/components/maven_installation_spec.js b/spec/frontend/packages/details/components/maven_installation_spec.js
index ff62969e709..dfeb6002186 100644
--- a/spec/frontend/packages/details/components/maven_installation_spec.js
+++ b/spec/frontend/packages/details/components/maven_installation_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { registryUrl as mavenPath } from 'jest/packages/details/mock_data';
import { mavenPackage as packageEntity } from 'jest/packages/mock_data';
import MavenInstallation from '~/packages/details/components/maven_installation.vue';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
import { TrackingActions } from '~/packages/details/constants';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/packages/details/components/npm_installation_spec.js b/spec/frontend/packages/details/components/npm_installation_spec.js
index dd6e62185a9..df820e7e948 100644
--- a/spec/frontend/packages/details/components/npm_installation_spec.js
+++ b/spec/frontend/packages/details/components/npm_installation_spec.js
@@ -1,11 +1,11 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { npmPackage as packageEntity } from 'jest/packages/mock_data';
+import Vuex from 'vuex';
import { registryUrl as nugetPath } from 'jest/packages/details/mock_data';
+import { npmPackage as packageEntity } from 'jest/packages/mock_data';
import NpmInstallation from '~/packages/details/components/npm_installation.vue';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
import { TrackingActions } from '~/packages/details/constants';
import { npmInstallationCommand, npmSetupCommand } from '~/packages/details/store/getters';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/packages/details/components/nuget_installation_spec.js b/spec/frontend/packages/details/components/nuget_installation_spec.js
index 685d0808dd9..100e369751c 100644
--- a/spec/frontend/packages/details/components/nuget_installation_spec.js
+++ b/spec/frontend/packages/details/components/nuget_installation_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nugetPackage as packageEntity } from 'jest/packages/mock_data';
+import Vuex from 'vuex';
import { registryUrl as nugetPath } from 'jest/packages/details/mock_data';
+import { nugetPackage as packageEntity } from 'jest/packages/mock_data';
import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
import { TrackingActions } from '~/packages/details/constants';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/packages/details/components/package_files_spec.js b/spec/frontend/packages/details/components/package_files_spec.js
index 9bcf6ed9235..b4e62bac8a3 100644
--- a/spec/frontend/packages/details/components/package_files_spec.js
+++ b/spec/frontend/packages/details/components/package_files_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
import stubChildren from 'helpers/stub_children';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import FileIcon from '~/vue_shared/components/file_icon.vue';
import component from '~/packages/details/components/package_files.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { npmFiles, mavenFiles } from '../../mock_data';
diff --git a/spec/frontend/packages/details/components/package_history_spec.js b/spec/frontend/packages/details/components/package_history_spec.js
index 5a6b386e2ca..244805a9c82 100644
--- a/spec/frontend/packages/details/components/package_history_spec.js
+++ b/spec/frontend/packages/details/components/package_history_spec.js
@@ -1,10 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
-import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
import component from '~/packages/details/components/package_history.vue';
+import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mavenPackage, mockPipelineInfo } from '../../mock_data';
diff --git a/spec/frontend/packages/details/components/package_title_spec.js b/spec/frontend/packages/details/components/package_title_spec.js
index 61c6e824ab7..512cec85b40 100644
--- a/spec/frontend/packages/details/components/package_title_spec.js
+++ b/spec/frontend/packages/details/components/package_title_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import PackageTitle from '~/packages/details/components/package_title.vue';
import PackageTags from '~/packages/shared/components/package_tags.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
diff --git a/spec/frontend/packages/details/components/pypi_installation_spec.js b/spec/frontend/packages/details/components/pypi_installation_spec.js
index da30b4ba565..a6ccba71554 100644
--- a/spec/frontend/packages/details/components/pypi_installation_spec.js
+++ b/spec/frontend/packages/details/components/pypi_installation_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { pypiPackage as packageEntity } from 'jest/packages/mock_data';
import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
diff --git a/spec/frontend/packages/details/store/actions_spec.js b/spec/frontend/packages/details/store/actions_spec.js
index e823a00ebc4..d11ee548b72 100644
--- a/spec/frontend/packages/details/store/actions_spec.js
+++ b/spec/frontend/packages/details/store/actions_spec.js
@@ -1,9 +1,9 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages/details/constants';
import { fetchPackageVersions, deletePackage } from '~/packages/details/store/actions';
import * as types from '~/packages/details/store/mutation_types';
-import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages/details/constants';
import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
import { npmPackage as packageEntity } from '../../mock_data';
diff --git a/spec/frontend/packages/details/store/getters_spec.js b/spec/frontend/packages/details/store/getters_spec.js
index b8c2138e7f5..07c120f57f7 100644
--- a/spec/frontend/packages/details/store/getters_spec.js
+++ b/spec/frontend/packages/details/store/getters_spec.js
@@ -1,3 +1,4 @@
+import { NpmManager } from '~/packages/details/constants';
import {
conanInstallationCommand,
conanSetupCommand,
@@ -32,7 +33,6 @@ import {
registryUrl,
pypiSetupCommandStr,
} from '../mock_data';
-import { NpmManager } from '~/packages/details/constants';
describe('Getters PackageDetails Store', () => {
let state;
diff --git a/spec/frontend/packages/details/store/mutations_spec.js b/spec/frontend/packages/details/store/mutations_spec.js
index 501a56dcdde..6bc5fb7241f 100644
--- a/spec/frontend/packages/details/store/mutations_spec.js
+++ b/spec/frontend/packages/details/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/packages/details/store/mutations';
import * as types from '~/packages/details/store/mutation_types';
+import mutations from '~/packages/details/store/mutations';
import { npmPackage as packageEntity } from '../../mock_data';
describe('Mutations package details Store', () => {
diff --git a/spec/frontend/packages/list/components/__snapshots__/packages_filter_spec.js.snap b/spec/frontend/packages/list/components/__snapshots__/packages_filter_spec.js.snap
deleted file mode 100644
index ed77f25916f..00000000000
--- a/spec/frontend/packages/list/components/__snapshots__/packages_filter_spec.js.snap
+++ /dev/null
@@ -1,14 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`packages_filter renders 1`] = `
-<gl-search-box-by-click-stub
- clearable="true"
- clearbuttontitle="Clear"
- clearrecentsearchestext="Clear recent searches"
- closebuttontitle="Close"
- norecentsearchestext="You don't have any recent searches"
- placeholder="Filter by name"
- recentsearchesheader="Recent searches"
- value=""
-/>
-`;
diff --git a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
index b2df1ac5ab6..3f17731584c 100644
--- a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
+++ b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -6,517 +6,60 @@ exports[`packages_list_app renders 1`] = `
packagehelpurl="foo"
/>
- <b-tabs-stub
- activenavitemclass="gl-tab-nav-item-active gl-tab-nav-item-active-indigo"
- class="gl-tabs"
- contentclass=",gl-tab-content"
- navclass=",gl-tabs-nav"
- nofade="true"
- nonavstyle="true"
- tag="div"
- >
- <template>
-
- <b-tab-stub
- tag="div"
- title="All"
- titlelinkclass="gl-tab-nav-item"
- >
- <template>
- <div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="Composer"
- titlelinkclass="gl-tab-nav-item"
- >
- <template>
- <div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no Composer packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="Conan"
- titlelinkclass="gl-tab-nav-item"
- >
- <template>
- <div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no Conan packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="Generic"
- titlelinkclass="gl-tab-nav-item"
- >
- <template>
- <div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no Generic packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="Maven"
- titlelinkclass="gl-tab-nav-item"
- >
- <template>
- <div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no Maven packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="NPM"
- titlelinkclass="gl-tab-nav-item"
+ <package-search-stub />
+
+ <div>
+ <section
+ class="row empty-state text-center"
+ >
+ <div
+ class="col-12"
>
- <template>
- <div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no NPM packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="NuGet"
- titlelinkclass="gl-tab-nav-item"
+ <div
+ class="svg-250 svg-content"
+ >
+ <img
+ alt=""
+ class="gl-max-w-full"
+ src="helpSvg"
+ />
+ </div>
+ </div>
+
+ <div
+ class="col-12"
>
- <template>
- <div>
- <section
- class="row empty-state text-center"
+ <div
+ class="text-content gl-mx-auto gl-my-0 gl-p-5"
+ >
+ <h1
+ class="h4"
+ >
+ There are no packages yet
+ </h1>
+
+ <p>
+ Learn how to
+ <b-link-stub
+ class="gl-link"
+ event="click"
+ href="helpUrl"
+ routertag="a"
+ target="_blank"
>
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no NuGet packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
- </div>
- </template>
- </b-tab-stub>
- <b-tab-stub
- tag="div"
- title="PyPI"
- titlelinkclass="gl-tab-nav-item"
- >
- <template>
+ publish and share your packages
+ </b-link-stub>
+ with GitLab.
+ </p>
+
<div>
- <section
- class="row empty-state text-center"
- >
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt=""
- class="gl-max-w-full"
- src="helpSvg"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content gl-mx-auto gl-my-0 gl-p-5"
- >
- <h1
- class="h4"
- >
- There are no PyPI packages yet
- </h1>
-
- <p>
- Learn how to
- <b-link-stub
- class="gl-link"
- event="click"
- href="helpUrl"
- routertag="a"
- target="_blank"
- >
- publish and share your packages
- </b-link-stub>
- with GitLab.
- </p>
-
- <div>
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
- </section>
+ <!---->
+
+ <!---->
</div>
- </template>
- </b-tab-stub>
- </template>
- <template>
- <div
- class="gl-display-flex gl-align-self-center gl-py-2 gl-flex-grow-1 gl-justify-content-end"
- >
- <package-filter-stub
- class="gl-mr-2"
- />
-
- <package-sort-stub />
+ </div>
</div>
- </template>
- </b-tabs-stub>
+ </section>
+ </div>
</div>
`;
diff --git a/spec/frontend/packages/list/components/packages_filter_spec.js b/spec/frontend/packages/list/components/packages_filter_spec.js
deleted file mode 100644
index b186b5f5e48..00000000000
--- a/spec/frontend/packages/list/components/packages_filter_spec.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import Vuex from 'vuex';
-import { GlSearchBoxByClick } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import PackagesFilter from '~/packages/list/components/packages_filter.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('packages_filter', () => {
- let wrapper;
- let store;
-
- const findGlSearchBox = () => wrapper.find(GlSearchBoxByClick);
-
- const mountComponent = () => {
- store = new Vuex.Store();
- store.dispatch = jest.fn();
-
- wrapper = shallowMount(PackagesFilter, {
- localVue,
- store,
- });
- };
-
- beforeEach(mountComponent);
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('renders', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('emits events', () => {
- it('sets the filter value in the store on input', () => {
- const searchString = 'foo';
- findGlSearchBox().vm.$emit('input', searchString);
-
- expect(store.dispatch).toHaveBeenCalledWith('setFilter', searchString);
- });
-
- it('emits the filter event when search box is submitted', () => {
- findGlSearchBox().vm.$emit('submit');
-
- expect(wrapper.emitted('filter')).toBeTruthy();
- });
- });
-});
diff --git a/spec/frontend/packages/list/components/packages_list_app_spec.js b/spec/frontend/packages/list/components/packages_list_app_spec.js
index 217096f822a..6862d23c4ff 100644
--- a/spec/frontend/packages/list/components/packages_list_app_spec.js
+++ b/spec/frontend/packages/list/components/packages_list_app_spec.js
@@ -1,11 +1,12 @@
-import Vuex from 'vuex';
+import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { GlEmptyState, GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
-import * as commonUtils from '~/lib/utils/common_utils';
+import Vuex from 'vuex';
import createFlash from '~/flash';
+import * as commonUtils from '~/lib/utils/common_utils';
+import PackageSearch from '~/packages/list/components/package_search.vue';
import PackageListApp from '~/packages/list/components/packages_list_app.vue';
-import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
+import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
@@ -26,9 +27,9 @@ describe('packages_list_app', () => {
const emptyListHelpUrl = 'helpUrl';
const findEmptyState = () => wrapper.find(GlEmptyState);
const findListComponent = () => wrapper.find(PackageList);
- const findTabComponent = (index = 0) => wrapper.findAll(GlTab).at(index);
+ const findPackageSearch = () => wrapper.find(PackageSearch);
- const createStore = (filterQuery = '') => {
+ const createStore = (filter = []) => {
store = new Vuex.Store({
state: {
isLoading: false,
@@ -38,7 +39,7 @@ describe('packages_list_app', () => {
emptyListHelpUrl,
packageHelpUrl: 'foo',
},
- filterQuery,
+ filter,
},
});
store.dispatch = jest.fn();
@@ -52,8 +53,6 @@ describe('packages_list_app', () => {
GlEmptyState,
GlLoadingIcon,
PackageList,
- GlTab,
- GlTabs,
GlSprintf,
GlLink,
},
@@ -94,6 +93,7 @@ describe('packages_list_app', () => {
it('call requestPackagesList on page:changed', () => {
mountComponent();
+ store.dispatch.mockClear();
const list = findListComponent();
list.vm.$emit('page:changed', 1);
@@ -108,41 +108,15 @@ describe('packages_list_app', () => {
expect(store.dispatch).toHaveBeenCalledWith('requestDeletePackage', 'foo');
});
- it('calls requestPackagesList on sort:changed', () => {
- mountComponent();
-
- const list = findListComponent();
- list.vm.$emit('sort:changed');
- expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
- });
-
it('does not call requestPackagesList two times on render', () => {
mountComponent();
expect(store.dispatch).toHaveBeenCalledTimes(1);
});
- describe('tab change', () => {
- it('calls requestPackagesList when all tab is clicked', () => {
- mountComponent();
-
- findTabComponent().trigger('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
- });
-
- it('calls requestPackagesList when a package type tab is clicked', () => {
- mountComponent();
-
- findTabComponent(1).trigger('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
- });
- });
-
describe('filter without results', () => {
beforeEach(() => {
- createStore('foo');
+ createStore([{ type: 'something' }]);
mountComponent();
});
@@ -154,12 +128,29 @@ describe('packages_list_app', () => {
});
});
+ describe('Package Search', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findPackageSearch().exists()).toBe(true);
+ });
+
+ it('on update fetches data from the store', () => {
+ mountComponent();
+ store.dispatch.mockClear();
+
+ findPackageSearch().vm.$emit('update');
+
+ expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
+ });
+ });
+
describe('delete alert handling', () => {
const { location } = window.location;
const search = `?${SHOW_DELETE_SUCCESS_ALERT}=true`;
beforeEach(() => {
- createStore('foo');
+ createStore();
jest.spyOn(commonUtils, 'historyReplaceState').mockImplementation(() => {});
delete window.location;
window.location = {
diff --git a/spec/frontend/packages/list/components/packages_list_spec.js b/spec/frontend/packages/list/components/packages_list_spec.js
index f981cc2851a..b1478a5e6dc 100644
--- a/spec/frontend/packages/list/components/packages_list_spec.js
+++ b/spec/frontend/packages/list/components/packages_list_spec.js
@@ -1,14 +1,14 @@
-import Vuex from 'vuex';
-import { last } from 'lodash';
import { GlTable, GlPagination, GlModal } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
+import { last } from 'lodash';
+import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
-import Tracking from '~/tracking';
import PackagesList from '~/packages/list/components/packages_list.vue';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
-import * as SharedUtils from '~/packages/shared/utils';
+import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import { TrackingActions } from '~/packages/shared/constants';
+import * as SharedUtils from '~/packages/shared/utils';
+import Tracking from '~/tracking';
import { packageList } from '../../mock_data';
const localVue = createLocalVue();
diff --git a/spec/frontend/packages/list/components/packages_search_spec.js b/spec/frontend/packages/list/components/packages_search_spec.js
new file mode 100644
index 00000000000..9b62dde8d2b
--- /dev/null
+++ b/spec/frontend/packages/list/components/packages_search_spec.js
@@ -0,0 +1,107 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import component from '~/packages/list/components/package_search.vue';
+import PackageTypeToken from '~/packages/list/components/tokens/package_type_token.vue';
+import getTableHeaders from '~/packages/list/utils';
+import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Package Search', () => {
+ let wrapper;
+ let store;
+
+ const findRegistrySearch = () => wrapper.find(RegistrySearch);
+
+ const createStore = (isGroupPage) => {
+ const state = {
+ config: {
+ isGroupPage,
+ },
+ sorting: {
+ orderBy: 'version',
+ sort: 'desc',
+ },
+ filter: [],
+ };
+ store = new Vuex.Store({
+ state,
+ });
+ store.dispatch = jest.fn();
+ };
+
+ const mountComponent = (isGroupPage = false) => {
+ createStore(isGroupPage);
+
+ wrapper = shallowMount(component, {
+ localVue,
+ store,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('has a registry search component', () => {
+ mountComponent();
+
+ expect(findRegistrySearch().exists()).toBe(true);
+ expect(findRegistrySearch().props()).toMatchObject({
+ filter: store.state.filter,
+ sorting: store.state.sorting,
+ tokens: expect.arrayContaining([
+ expect.objectContaining({ token: PackageTypeToken, type: 'type', icon: 'package' }),
+ ]),
+ sortableFields: getTableHeaders(),
+ });
+ });
+
+ it.each`
+ isGroupPage | page
+ ${false} | ${'project'}
+ ${true} | ${'group'}
+ `('in a $page page binds the right props', ({ isGroupPage }) => {
+ mountComponent(isGroupPage);
+
+ expect(findRegistrySearch().props()).toMatchObject({
+ filter: store.state.filter,
+ sorting: store.state.sorting,
+ tokens: expect.arrayContaining([
+ expect.objectContaining({ token: PackageTypeToken, type: 'type', icon: 'package' }),
+ ]),
+ sortableFields: getTableHeaders(isGroupPage),
+ });
+ });
+
+ it('on sorting:changed emits update event and calls vuex setSorting', () => {
+ const payload = { sort: 'foo' };
+
+ mountComponent();
+
+ findRegistrySearch().vm.$emit('sorting:changed', payload);
+
+ expect(store.dispatch).toHaveBeenCalledWith('setSorting', payload);
+ expect(wrapper.emitted('update')).toEqual([[]]);
+ });
+
+ it('on filter:changed calls vuex setFilter', () => {
+ const payload = ['foo'];
+
+ mountComponent();
+
+ findRegistrySearch().vm.$emit('filter:changed', payload);
+
+ expect(store.dispatch).toHaveBeenCalledWith('setFilter', payload);
+ });
+
+ it('on filter:submit emits update event', () => {
+ mountComponent();
+
+ findRegistrySearch().vm.$emit('filter:submit');
+
+ expect(wrapper.emitted('update')).toEqual([[]]);
+ });
+});
diff --git a/spec/frontend/packages/list/components/packages_sort_spec.js b/spec/frontend/packages/list/components/packages_sort_spec.js
deleted file mode 100644
index d15ad9bd542..00000000000
--- a/spec/frontend/packages/list/components/packages_sort_spec.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import Vuex from 'vuex';
-import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
-import stubChildren from 'helpers/stub_children';
-import PackagesSort from '~/packages/list/components/packages_sort.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('packages_sort', () => {
- let wrapper;
- let store;
- let sorting;
- let sortingItems;
-
- const findPackageListSorting = () => wrapper.find(GlSorting);
- const findSortingItems = () => wrapper.findAll(GlSortingItem);
-
- const createStore = (isGroupPage) => {
- const state = {
- config: {
- isGroupPage,
- },
- sorting: {
- orderBy: 'version',
- sort: 'desc',
- },
- };
- store = new Vuex.Store({
- state,
- });
- store.dispatch = jest.fn();
- };
-
- const mountComponent = (isGroupPage = false) => {
- createStore(isGroupPage);
-
- wrapper = mount(PackagesSort, {
- localVue,
- store,
- stubs: {
- ...stubChildren(PackagesSort),
- GlSortingItem,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when is in projects', () => {
- beforeEach(() => {
- mountComponent();
- sorting = findPackageListSorting();
- sortingItems = findSortingItems();
- });
-
- it('has all the sortable items', () => {
- expect(sortingItems).toHaveLength(wrapper.vm.sortableFields.length);
- });
-
- it('on sort change set sorting in vuex and emit event', () => {
- sorting.vm.$emit('sortDirectionChange');
- expect(store.dispatch).toHaveBeenCalledWith('setSorting', { sort: 'asc' });
- expect(wrapper.emitted('sort:changed')).toBeTruthy();
- });
-
- it('on sort item click set sorting and emit event', () => {
- const item = sortingItems.at(0);
- const { orderBy } = wrapper.vm.sortableFields[0];
- item.vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('setSorting', { orderBy });
- expect(wrapper.emitted('sort:changed')).toBeTruthy();
- });
- });
-
- describe('when is in group', () => {
- beforeEach(() => {
- mountComponent(true);
- sorting = findPackageListSorting();
- sortingItems = findSortingItems();
- });
-
- it('has all the sortable items', () => {
- expect(sortingItems).toHaveLength(wrapper.vm.sortableFields.length);
- });
- });
-});
diff --git a/spec/frontend/packages/list/components/packages_title_spec.js b/spec/frontend/packages/list/components/packages_title_spec.js
index 5e9ebd8ecb0..3716e8daa7c 100644
--- a/spec/frontend/packages/list/components/packages_title_spec.js
+++ b/spec/frontend/packages/list/components/packages_title_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
+import { LIST_INTRO_TEXT, LIST_TITLE_TEXT } from '~/packages/list//constants';
import PackageTitle from '~/packages/list/components/package_title.vue';
-import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
-import { LIST_INTRO_TEXT, LIST_TITLE_TEXT } from '~/packages/list//constants';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
describe('PackageTitle', () => {
let wrapper;
diff --git a/spec/frontend/packages/list/components/tokens/package_type_token_spec.js b/spec/frontend/packages/list/components/tokens/package_type_token_spec.js
new file mode 100644
index 00000000000..b0cbe34f0b9
--- /dev/null
+++ b/spec/frontend/packages/list/components/tokens/package_type_token_spec.js
@@ -0,0 +1,48 @@
+import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/packages/list/components/tokens/package_type_token.vue';
+import { PACKAGE_TYPES } from '~/packages/list/constants';
+
+describe('packages_filter', () => {
+ let wrapper;
+
+ const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
+ const findFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
+
+ const mountComponent = ({ attrs, listeners } = {}) => {
+ wrapper = shallowMount(component, {
+ attrs,
+ listeners,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('it binds all of his attrs to filtered search token', () => {
+ mountComponent({ attrs: { foo: 'bar' } });
+
+ expect(findFilteredSearchToken().attributes('foo')).toBe('bar');
+ });
+
+ it('it binds all of his events to filtered search token', () => {
+ const clickListener = jest.fn();
+ mountComponent({ listeners: { click: clickListener } });
+
+ findFilteredSearchToken().vm.$emit('click');
+
+ expect(clickListener).toHaveBeenCalled();
+ });
+
+ it.each(PACKAGE_TYPES.map((p, index) => [p, index]))(
+ 'displays a suggestion for %p',
+ (packageType, index) => {
+ mountComponent();
+ const item = findFilteredSearchSuggestions().at(index);
+ expect(item.text()).toBe(packageType.title);
+ expect(item.props('value')).toBe(packageType.type);
+ },
+ );
+});
diff --git a/spec/frontend/packages/list/stores/actions_spec.js b/spec/frontend/packages/list/stores/actions_spec.js
index 05e1fe57cae..b5b0177eb4e 100644
--- a/spec/frontend/packages/list/stores/actions_spec.js
+++ b/spec/frontend/packages/list/stores/actions_spec.js
@@ -3,9 +3,9 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { MISSING_DELETE_PATH_ERROR } from '~/packages/list/constants';
import * as actions from '~/packages/list/stores/actions';
import * as types from '~/packages/list/stores/mutation_types';
-import { MISSING_DELETE_PATH_ERROR } from '~/packages/list/constants';
import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
jest.mock('~/flash.js');
@@ -30,11 +30,13 @@ describe('Actions Package list store', () => {
sort: 'asc',
orderBy: 'version',
};
+
+ const filter = [];
it('should fetch the project packages list when isGroupPage is false', (done) => {
testAction(
actions.requestPackagesList,
undefined,
- { config: { isGroupPage: false, resourceId: 1 }, sorting },
+ { config: { isGroupPage: false, resourceId: 1 }, sorting, filter },
[],
[
{ type: 'setLoading', payload: true },
@@ -54,7 +56,7 @@ describe('Actions Package list store', () => {
testAction(
actions.requestPackagesList,
undefined,
- { config: { isGroupPage: true, resourceId: 2 }, sorting },
+ { config: { isGroupPage: true, resourceId: 2 }, sorting, filter },
[],
[
{ type: 'setLoading', payload: true },
@@ -70,7 +72,7 @@ describe('Actions Package list store', () => {
);
});
- it('should fetch packages of a certain type when selectedType is present', (done) => {
+ it('should fetch packages of a certain type when a filter with a type is present', (done) => {
const packageType = 'maven';
testAction(
@@ -79,7 +81,7 @@ describe('Actions Package list store', () => {
{
config: { isGroupPage: false, resourceId: 1 },
sorting,
- selectedType: { type: packageType },
+ filter: [{ type: 'type', value: { data: 'maven' } }],
},
[],
[
@@ -107,7 +109,7 @@ describe('Actions Package list store', () => {
testAction(
actions.requestPackagesList,
undefined,
- { config: { isGroupPage: false, resourceId: 2 }, sorting },
+ { config: { isGroupPage: false, resourceId: 2 }, sorting, filter },
[],
[
{ type: 'setLoading', payload: true },
diff --git a/spec/frontend/packages/list/stores/mutations_spec.js b/spec/frontend/packages/list/stores/mutations_spec.js
index 0d424a0c011..2ddf3a1da33 100644
--- a/spec/frontend/packages/list/stores/mutations_spec.js
+++ b/spec/frontend/packages/list/stores/mutations_spec.js
@@ -1,7 +1,7 @@
-import mutations from '~/packages/list/stores/mutations';
+import * as commonUtils from '~/lib/utils/common_utils';
import * as types from '~/packages/list/stores/mutation_types';
+import mutations from '~/packages/list/stores/mutations';
import createState from '~/packages/list/stores/state';
-import * as commonUtils from '~/lib/utils/common_utils';
import { npmPackage, mavenPackage } from '../../mock_data';
describe('Mutations Registry Store', () => {
@@ -78,17 +78,10 @@ describe('Mutations Registry Store', () => {
});
});
- describe('SET_SELECTED_TYPE', () => {
- it('should set the selected type', () => {
- mutations[types.SET_SELECTED_TYPE](mockState, { type: 'maven' });
- expect(mockState.selectedType).toEqual({ type: 'maven' });
- });
- });
-
describe('SET_FILTER', () => {
it('should set the filter query', () => {
mutations[types.SET_FILTER](mockState, 'foo');
- expect(mockState.filterQuery).toEqual('foo');
+ expect(mockState.filter).toEqual('foo');
});
});
});
diff --git a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap
index 5faae5690db..4a75deebcf9 100644
--- a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap
+++ b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap
@@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = `
data-qa-selector="package_row"
>
<div
- class="gl-display-flex gl-align-items-center gl-py-5"
+ class="gl-display-flex gl-align-items-center gl-py-3"
>
<!---->
@@ -14,7 +14,7 @@ exports[`packages_list_row renders 1`] = `
class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1"
>
<div
- class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1"
+ class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1"
>
<div
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0"
@@ -40,7 +40,7 @@ exports[`packages_list_row renders 1`] = `
</div>
<div
- class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1"
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-fill-1"
>
<div
class="gl-display-flex"
@@ -85,7 +85,7 @@ exports[`packages_list_row renders 1`] = `
</div>
<div
- class="gl-display-flex gl-align-items-center gl-mt-1 gl-min-h-6"
+ class="gl-display-flex gl-align-items-center gl-min-h-6"
>
<span>
<gl-sprintf-stub
@@ -97,7 +97,7 @@ exports[`packages_list_row renders 1`] = `
</div>
<div
- class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-1"
+ class="gl-w-9 gl-display-none gl-sm-display-flex gl-justify-content-end gl-pr-1"
>
<gl-button-stub
aria-label="Remove package"
diff --git a/spec/frontend/packages/shared/components/package_list_row_spec.js b/spec/frontend/packages/shared/components/package_list_row_spec.js
index 0d0ea4e2122..bd122167273 100644
--- a/spec/frontend/packages/shared/components/package_list_row_spec.js
+++ b/spec/frontend/packages/shared/components/package_list_row_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
import PackagePath from '~/packages/shared/components/package_path.vue';
+import PackageTags from '~/packages/shared/components/package_tags.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { packageList } from '../../mock_data';
diff --git a/spec/frontend/packages/shared/components/packages_list_loader_spec.js b/spec/frontend/packages/shared/components/packages_list_loader_spec.js
index 115a3a7095d..4ff01068f92 100644
--- a/spec/frontend/packages/shared/components/packages_list_loader_spec.js
+++ b/spec/frontend/packages/shared/components/packages_list_loader_spec.js
@@ -30,7 +30,7 @@ describe('PackagesListLoader', () => {
it('has the correct classes', () => {
expect(findDesktopShapes().classes()).toEqual([
'gl-display-none',
- 'gl-display-sm-flex',
+ 'gl-sm-display-flex',
'gl-flex-direction-column',
]);
});
@@ -44,7 +44,7 @@ describe('PackagesListLoader', () => {
it('has the correct classes', () => {
expect(findMobileShapes().classes()).toEqual([
'gl-flex-direction-column',
- 'gl-display-sm-none',
+ 'gl-sm-display-none',
]);
});
});
diff --git a/spec/frontend/packages/shared/utils_spec.js b/spec/frontend/packages/shared/utils_spec.js
index 590e3814bee..506f37f8895 100644
--- a/spec/frontend/packages/shared/utils_spec.js
+++ b/spec/frontend/packages/shared/utils_spec.js
@@ -1,10 +1,10 @@
+import { PackageType, TrackingCategories } from '~/packages/shared/constants';
import {
packageTypeToTrackCategory,
beautifyPath,
getPackageTypeLabel,
getCommitLink,
} from '~/packages/shared/utils';
-import { PackageType, TrackingCategories } from '~/packages/shared/constants';
import { packageList } from '../mock_data';
describe('Packages shared utils', () => {
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
new file mode 100644
index 00000000000..be0d7114e6e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
@@ -0,0 +1,309 @@
+import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
+import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
+import {
+ PACKAGE_SETTINGS_HEADER,
+ PACKAGE_SETTINGS_DESCRIPTION,
+ PACKAGES_DOCS_PATH,
+ ERROR_UPDATING_SETTINGS,
+ SUCCESS_UPDATING_SETTINGS,
+} from '~/packages_and_registries/settings/group/constants';
+
+import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql';
+import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
+import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
+import {
+ groupPackageSettingsMock,
+ groupPackageSettingsMutationMock,
+ groupPackageSettingsMutationErrorMock,
+} from '../mock_data';
+
+jest.mock('~/flash');
+
+const localVue = createLocalVue();
+
+describe('Group Settings App', () => {
+ let wrapper;
+ let apolloProvider;
+ let show;
+
+ const defaultProvide = {
+ defaultExpanded: false,
+ groupPath: 'foo_group_path',
+ };
+
+ const mountComponent = ({
+ provide = defaultProvide,
+ resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock),
+ mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()),
+ data = {},
+ } = {}) => {
+ localVue.use(VueApollo);
+
+ const requestHandlers = [
+ [getGroupPackagesSettingsQuery, resolver],
+ [updateNamespacePackageSettings, mutationResolver],
+ ];
+
+ apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMount(component, {
+ localVue,
+ apolloProvider,
+ provide,
+ data() {
+ return {
+ ...data,
+ };
+ },
+ stubs: {
+ GlSprintf,
+ SettingsBlock,
+ },
+ mocks: {
+ $toast: {
+ show,
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ show = jest.fn();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findSettingsBlock = () => wrapper.find(SettingsBlock);
+ const findDescription = () => wrapper.find('[data-testid="description"');
+ const findLink = () => wrapper.find(GlLink);
+ const findMavenSettings = () => wrapper.find(MavenSettings);
+ const findAlert = () => wrapper.find(GlAlert);
+
+ const waitForApolloQueryAndRender = async () => {
+ await waitForPromises();
+ await wrapper.vm.$nextTick();
+ };
+
+ const emitSettingsUpdate = (override) => {
+ findMavenSettings().vm.$emit('update', {
+ mavenDuplicateExceptionRegex: ')',
+ ...override,
+ });
+ };
+
+ it('renders a settings block', () => {
+ mountComponent();
+
+ expect(findSettingsBlock().exists()).toBe(true);
+ });
+
+ it('passes the correct props to settings block', () => {
+ mountComponent();
+
+ expect(findSettingsBlock().props('defaultExpanded')).toBe(false);
+ });
+
+ it('has the correct header text', () => {
+ mountComponent();
+
+ expect(wrapper.text()).toContain(PACKAGE_SETTINGS_HEADER);
+ });
+
+ it('has the correct description text', () => {
+ mountComponent();
+
+ expect(findDescription().text()).toMatchInterpolatedText(PACKAGE_SETTINGS_DESCRIPTION);
+ });
+
+ it('has the correct link', () => {
+ mountComponent();
+
+ expect(findLink().attributes()).toMatchObject({
+ href: PACKAGES_DOCS_PATH,
+ target: '_blank',
+ });
+ expect(findLink().text()).toBe('More Information');
+ });
+
+ it('calls the graphql API with the proper variables', () => {
+ const resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock);
+ mountComponent({ resolver });
+
+ expect(resolver).toHaveBeenCalledWith({
+ fullPath: defaultProvide.groupPath,
+ });
+ });
+
+ describe('maven settings', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findMavenSettings().exists()).toBe(true);
+ });
+
+ it('assigns duplication allowness and exception props', async () => {
+ mountComponent();
+
+ expect(findMavenSettings().props('loading')).toBe(true);
+
+ await waitForApolloQueryAndRender();
+
+ const {
+ mavenDuplicatesAllowed,
+ mavenDuplicateExceptionRegex,
+ } = groupPackageSettingsMock.data.group.packageSettings;
+
+ expect(findMavenSettings().props()).toMatchObject({
+ mavenDuplicatesAllowed,
+ mavenDuplicateExceptionRegex,
+ mavenDuplicateExceptionRegexError: '',
+ loading: false,
+ });
+ });
+
+ it('on update event calls the mutation', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
+ mountComponent({ mutationResolver });
+
+ await waitForApolloQueryAndRender();
+
+ emitSettingsUpdate();
+
+ expect(mutationResolver).toHaveBeenCalledWith({
+ input: { mavenDuplicateExceptionRegex: ')', namespacePath: 'foo_group_path' },
+ });
+ });
+ });
+
+ describe('settings update', () => {
+ describe('success state', () => {
+ it('shows a success alert', async () => {
+ mountComponent();
+
+ await waitForApolloQueryAndRender();
+
+ emitSettingsUpdate();
+
+ await waitForPromises();
+
+ expect(show).toHaveBeenCalledWith(SUCCESS_UPDATING_SETTINGS, {
+ type: 'success',
+ });
+ });
+
+ it('has an optimistic response', async () => {
+ const mavenDuplicateExceptionRegex = 'latest[master]something';
+ mountComponent();
+
+ await waitForApolloQueryAndRender();
+
+ expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe('');
+
+ emitSettingsUpdate({ mavenDuplicateExceptionRegex });
+
+ // wait for apollo to update the model with the optimistic response
+ await wrapper.vm.$nextTick();
+
+ expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe(
+ mavenDuplicateExceptionRegex,
+ );
+
+ // wait for the call to resolve
+ await waitForPromises();
+
+ expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe(
+ mavenDuplicateExceptionRegex,
+ );
+ });
+ });
+
+ describe('errors', () => {
+ const verifyAlert = () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(ERROR_UPDATING_SETTINGS);
+ expect(findAlert().props('variant')).toBe('warning');
+ };
+
+ it('mutation payload with root level errors', async () => {
+ // note this is a complex test that covers all the path around errors that are shown in the form
+ // it's one single it case, due to the expensive preparation and execution
+ const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationErrorMock);
+ mountComponent({ mutationResolver });
+
+ await waitForApolloQueryAndRender();
+
+ emitSettingsUpdate();
+
+ await waitForApolloQueryAndRender();
+
+ // errors are bound to the component
+ expect(findMavenSettings().props('mavenDuplicateExceptionRegexError')).toBe(
+ groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message,
+ );
+
+ // general error message is shown
+
+ verifyAlert();
+
+ emitSettingsUpdate();
+
+ await wrapper.vm.$nextTick();
+
+ // errors are reset on mutation call
+ expect(findMavenSettings().props('mavenDuplicateExceptionRegexError')).toBe('');
+ });
+
+ it.each`
+ type | mutationResolver
+ ${'local'} | ${jest.fn().mockResolvedValue(groupPackageSettingsMutationMock({ errors: ['foo'] }))}
+ ${'network'} | ${jest.fn().mockRejectedValue()}
+ `('mutation payload with $type error', async ({ mutationResolver }) => {
+ mountComponent({ mutationResolver });
+
+ await waitForApolloQueryAndRender();
+
+ emitSettingsUpdate();
+
+ await waitForPromises();
+
+ verifyAlert();
+ });
+
+ it('a successful request dismisses the alert', async () => {
+ mountComponent({ data: { alertMessage: 'foo' } });
+
+ await waitForApolloQueryAndRender();
+
+ expect(findAlert().exists()).toBe(true);
+
+ emitSettingsUpdate();
+
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('dismiss event from alert dismiss it from the page', async () => {
+ mountComponent({ data: { alertMessage: 'foo' } });
+
+ await waitForApolloQueryAndRender();
+
+ expect(findAlert().exists()).toBe(true);
+
+ findAlert().vm.$emit('dismiss');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js
new file mode 100644
index 00000000000..2433c50ff24
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js
@@ -0,0 +1,153 @@
+import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/packages_and_registries/settings/group/components/maven_settings.vue';
+
+import {
+ MAVEN_TITLE,
+ MAVEN_SETTINGS_SUBTITLE,
+ MAVEN_DUPLICATES_ALLOWED_DISABLED,
+ MAVEN_DUPLICATES_ALLOWED_ENABLED,
+ MAVEN_SETTING_EXCEPTION_TITLE,
+ MAVEN_SETTINGS_EXCEPTION_LEGEND,
+} from '~/packages_and_registries/settings/group/constants';
+
+describe('Maven Settings', () => {
+ let wrapper;
+
+ const defaultProps = {
+ mavenDuplicatesAllowed: false,
+ mavenDuplicateExceptionRegex: 'foo',
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findTitle = () => wrapper.find('h5');
+ const findSubTitle = () => wrapper.find('p');
+ const findToggle = () => wrapper.find(GlToggle);
+ const findToggleLabel = () => wrapper.find('[data-testid="toggle-label"');
+
+ const findInputGroup = () => wrapper.find(GlFormGroup);
+ const findInput = () => wrapper.find(GlFormInput);
+
+ it('has a title', () => {
+ mountComponent();
+
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().text()).toBe(MAVEN_TITLE);
+ });
+
+ it('has a subtitle', () => {
+ mountComponent();
+
+ expect(findSubTitle().exists()).toBe(true);
+ expect(findSubTitle().text()).toBe(MAVEN_SETTINGS_SUBTITLE);
+ });
+
+ it('has a toggle', () => {
+ mountComponent();
+
+ expect(findToggle().exists()).toBe(true);
+ expect(findToggle().props('value')).toBe(defaultProps.mavenDuplicatesAllowed);
+ });
+
+ it('toggle emits an update event', () => {
+ mountComponent();
+
+ findToggle().vm.$emit('change', false);
+
+ expect(wrapper.emitted('update')).toEqual([[{ mavenDuplicatesAllowed: false }]]);
+ });
+
+ describe('when the duplicates are disabled', () => {
+ it('the toggle has the disabled message', () => {
+ mountComponent();
+
+ expect(findToggleLabel().exists()).toBe(true);
+ expect(findToggleLabel().text()).toMatchInterpolatedText(MAVEN_DUPLICATES_ALLOWED_DISABLED);
+ });
+
+ it('shows a form group with an input field', () => {
+ mountComponent();
+
+ expect(findInputGroup().exists()).toBe(true);
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ 'label-for': 'maven-duplicated-settings-regex-input',
+ label: MAVEN_SETTING_EXCEPTION_TITLE,
+ description: MAVEN_SETTINGS_EXCEPTION_LEGEND,
+ });
+ });
+
+ it('shows an input field', () => {
+ mountComponent();
+
+ expect(findInput().exists()).toBe(true);
+
+ expect(findInput().attributes()).toMatchObject({
+ id: 'maven-duplicated-settings-regex-input',
+ value: defaultProps.mavenDuplicateExceptionRegex,
+ });
+ });
+
+ it('input change event emits an update event', () => {
+ mountComponent();
+
+ findInput().vm.$emit('change', 'bar');
+
+ expect(wrapper.emitted('update')).toEqual([[{ mavenDuplicateExceptionRegex: 'bar' }]]);
+ });
+
+ describe('valid state', () => {
+ it('form group has correct props', () => {
+ mountComponent();
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ state: 'true',
+ 'invalid-feedback': '',
+ });
+ });
+ });
+
+ describe('invalid state', () => {
+ it('form group has correct props', () => {
+ const propsWithError = {
+ ...defaultProps,
+ mavenDuplicateExceptionRegexError: 'some error string',
+ };
+
+ mountComponent(propsWithError);
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ 'invalid-feedback': propsWithError.mavenDuplicateExceptionRegexError,
+ });
+ });
+ });
+ });
+
+ describe('when the duplicates are enabled', () => {
+ it('has the correct toggle label', () => {
+ mountComponent({ ...defaultProps, mavenDuplicatesAllowed: true });
+
+ expect(findToggleLabel().exists()).toBe(true);
+ expect(findToggleLabel().text()).toMatchInterpolatedText(MAVEN_DUPLICATES_ALLOWED_ENABLED);
+ });
+
+ it('hides the form input group', () => {
+ mountComponent({ ...defaultProps, mavenDuplicatesAllowed: true });
+
+ expect(findInputGroup().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
new file mode 100644
index 00000000000..e1a46f97318
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
@@ -0,0 +1,56 @@
+import expirationPolicyQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
+import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
+
+describe('Package and Registries settings group cache updates', () => {
+ let client;
+
+ const payload = {
+ data: {
+ updateNamespacePackageSettings: {
+ packageSettings: {
+ mavenDuplicatesAllowed: false,
+ mavenDuplicateExceptionRegex: 'latest[master]something',
+ },
+ },
+ },
+ };
+
+ const cacheMock = {
+ group: {
+ packageSettings: {
+ mavenDuplicatesAllowed: true,
+ mavenDuplicateExceptionRegex: '',
+ },
+ },
+ };
+
+ const queryAndVariables = {
+ query: expirationPolicyQuery,
+ variables: { fullPath: 'foo' },
+ };
+
+ beforeEach(() => {
+ client = {
+ readQuery: jest.fn().mockReturnValue(cacheMock),
+ writeQuery: jest.fn(),
+ };
+ });
+ describe('updateGroupPackageSettings', () => {
+ it('calls readQuery', () => {
+ updateGroupPackageSettings('foo')(client, payload);
+ expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables);
+ });
+
+ it('writes the correct result in the cache', () => {
+ updateGroupPackageSettings('foo')(client, payload);
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ ...queryAndVariables,
+ data: {
+ group: {
+ ...payload.data.updateNamespacePackageSettings,
+ },
+ },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
new file mode 100644
index 00000000000..a3c53d5768a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
@@ -0,0 +1,20 @@
+import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+
+describe('Optimistic responses', () => {
+ describe('updateGroupPackagesSettingsOptimisticResponse', () => {
+ it('returns the correct structure', () => {
+ expect(updateGroupPackagesSettingsOptimisticResponse({ foo: 'bar' })).toMatchInlineSnapshot(`
+ Object {
+ "__typename": "Mutation",
+ "updateNamespacePackageSettings": Object {
+ "__typename": "UpdateNamespacePackageSettingsPayload",
+ "errors": Array [],
+ "packageSettings": Object {
+ "foo": "bar",
+ },
+ },
+ }
+ `);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/mock_data.js b/spec/frontend/packages_and_registries/settings/group/mock_data.js
new file mode 100644
index 00000000000..777c0898de0
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js
@@ -0,0 +1,48 @@
+export const groupPackageSettingsMock = {
+ data: {
+ group: {
+ packageSettings: {
+ mavenDuplicatesAllowed: true,
+ mavenDuplicateExceptionRegex: '',
+ },
+ },
+ },
+};
+
+export const groupPackageSettingsMutationMock = (override) => ({
+ data: {
+ updateNamespacePackageSettings: {
+ packageSettings: {
+ mavenDuplicatesAllowed: true,
+ mavenDuplicateExceptionRegex: 'latest[master]something',
+ },
+ errors: [],
+ ...override,
+ },
+ },
+});
+
+export const groupPackageSettingsMutationErrorMock = {
+ errors: [
+ {
+ message:
+ 'Variable $input of type UpdateNamespacePackageSettingsInput! was provided invalid value for mavenDuplicateExceptionRegex (latest[master]somethingj)) is an invalid regexp: unexpected ): latest[master]somethingj)))',
+ locations: [{ line: 1, column: 41 }],
+ extensions: {
+ value: {
+ namespacePath: 'gitlab-org',
+ mavenDuplicateExceptionRegex: 'latest[master]something))',
+ },
+ problems: [
+ {
+ path: ['mavenDuplicateExceptionRegex'],
+ explanation:
+ 'latest[master]somethingj)) is an invalid regexp: unexpected ): latest[master]something))',
+ message:
+ 'latest[master]somethingj)) is an invalid regexp: unexpected ): latest[master]something))',
+ },
+ ],
+ },
+ },
+ ],
+};
diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js
index c8a129e38e7..ad4222e7cb2 100644
--- a/spec/frontend/pager_spec.js
+++ b/spec/frontend/pager_spec.js
@@ -1,9 +1,9 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import Pager from '~/pager';
import { removeParams } from '~/lib/utils/url_utility';
+import Pager from '~/pager';
jest.mock('~/lib/utils/url_utility', () => ({
removeParams: jest.fn().mockName('removeParams'),
diff --git a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
index 81750b4827f..52648d3ce00 100644
--- a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
+++ b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import { redirectTo } from '~/lib/utils/url_utility';
+import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
+import { redirectTo } from '~/lib/utils/url_utility';
import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
jest.mock('~/lib/utils/url_utility', () => ({
diff --git a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js
index d203a8ea0e0..c7293b00adf 100644
--- a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js
+++ b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DeleteUserModal from '~/pages/admin/users/components/delete_user_modal.vue';
import ModalStub from './stubs/modal_stub';
diff --git a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js
index 6df2efd624d..3669bc40d7e 100644
--- a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js
+++ b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js
@@ -3,6 +3,8 @@ import UserModalManager from '~/pages/admin/users/components/user_modal_manager.
import ModalStub from './stubs/modal_stub';
describe('Users admin page Modal Manager', () => {
+ let wrapper;
+
const modalConfiguration = {
action1: {
title: 'action1',
@@ -14,11 +16,12 @@ describe('Users admin page Modal Manager', () => {
},
};
- let wrapper;
+ const findModal = () => wrapper.find({ ref: 'modal' });
const createComponent = (props = {}) => {
wrapper = mount(UserModalManager, {
propsData: {
+ selector: '.js-delete-user-modal-button',
modalConfiguration,
csrfToken: 'dummyCSRF',
...props,
@@ -37,7 +40,7 @@ describe('Users admin page Modal Manager', () => {
describe('render behavior', () => {
it('does not renders modal when initialized', () => {
createComponent();
- expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy();
+ expect(findModal().exists()).toBeFalsy();
});
it('throws if action has no proper configuration', () => {
@@ -55,7 +58,7 @@ describe('Users admin page Modal Manager', () => {
});
return wrapper.vm.$nextTick().then(() => {
- const modal = wrapper.find({ ref: 'modal' });
+ const modal = findModal();
expect(modal.exists()).toBeTruthy();
expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF');
expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue');
@@ -64,68 +67,60 @@ describe('Users admin page Modal Manager', () => {
});
});
- describe('global listener', () => {
+ describe('click handling', () => {
+ let button;
+ let button2;
+
+ const createButtons = () => {
+ button = document.createElement('button');
+ button2 = document.createElement('button');
+ button.setAttribute('class', 'js-delete-user-modal-button');
+ button.setAttribute('data-username', 'foo');
+ button.setAttribute('data-gl-modal-action', 'action1');
+ button.setAttribute('data-block-user-url', '/block');
+ button.setAttribute('data-delete-user-url', '/delete');
+ document.body.appendChild(button);
+ document.body.appendChild(button2);
+ };
+ const removeButtons = () => {
+ button.remove();
+ button = null;
+ button2.remove();
+ button2 = null;
+ };
+
beforeEach(() => {
- jest.spyOn(document, 'addEventListener');
- jest.spyOn(document, 'removeEventListener');
+ createButtons();
+ createComponent();
});
- afterAll(() => {
- jest.restoreAllMocks();
+ afterEach(() => {
+ removeButtons();
});
- it('registers global listener on mount', () => {
- createComponent();
- expect(document.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
- });
+ it('renders the modal when the button is clicked', async () => {
+ button.click();
- it('removes global listener on destroy', () => {
- createComponent();
- wrapper.destroy();
- expect(document.removeEventListener).toHaveBeenCalledWith('click', expect.any(Function));
+ await wrapper.vm.$nextTick();
+
+ expect(findModal().exists()).toBe(true);
});
- });
- describe('click handling', () => {
- let node;
+ it('does not render the modal when a misconfigured button is clicked', async () => {
+ button.removeAttribute('data-gl-modal-action');
+ button.click();
- beforeEach(() => {
- node = document.createElement('div');
- document.body.appendChild(node);
- });
+ await wrapper.vm.$nextTick();
- afterEach(() => {
- node.remove();
- node = null;
+ expect(findModal().exists()).toBe(false);
});
- it('ignores wrong clicks', () => {
- createComponent();
- const event = new window.MouseEvent('click', {
- bubbles: true,
- cancellable: true,
- });
- jest.spyOn(event, 'preventDefault');
- node.dispatchEvent(event);
- expect(event.preventDefault).not.toHaveBeenCalled();
- });
+ it('does not render the modal when a button without the selector class is clicked', async () => {
+ button2.click();
- it('captures click with glModalAction', () => {
- createComponent();
- node.dataset.glModalAction = 'action1';
- const event = new window.MouseEvent('click', {
- bubbles: true,
- cancellable: true,
- });
- jest.spyOn(event, 'preventDefault');
- node.dispatchEvent(event);
+ await wrapper.vm.$nextTick();
- expect(event.preventDefault).toHaveBeenCalled();
- return wrapper.vm.$nextTick().then(() => {
- const modal = wrapper.find({ ref: 'modal' });
- expect(modal.exists()).toBeTruthy();
- expect(modal.vm.showWasCalled).toBeTruthy();
- });
+ expect(findModal().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/pages/dashboard/projects/index/components/customize_homepage_banner_spec.js b/spec/frontend/pages/dashboard/projects/index/components/customize_homepage_banner_spec.js
index fbe2274c40d..63c1260560b 100644
--- a/spec/frontend/pages/dashboard/projects/index/components/customize_homepage_banner_spec.js
+++ b/spec/frontend/pages/dashboard/projects/index/components/customize_homepage_banner_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlBanner } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
-import CustomizeHomepageBanner from '~/pages/dashboard/projects/index/components/customize_homepage_banner.vue';
import axios from '~/lib/utils/axios_utils';
+import CustomizeHomepageBanner from '~/pages/dashboard/projects/index/components/customize_homepage_banner.vue';
const svgPath = '/illustrations/background';
const provide = {
diff --git a/spec/frontend/pages/dashboard/todos/index/todos_spec.js b/spec/frontend/pages/dashboard/todos/index/todos_spec.js
index 5018b0c4f73..fb612f17669 100644
--- a/spec/frontend/pages/dashboard/todos/index/todos_spec.js
+++ b/spec/frontend/pages/dashboard/todos/index/todos_spec.js
@@ -1,10 +1,10 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
-import Todos from '~/pages/dashboard/todos/index/todos';
+import $ from 'jquery';
import '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
import { addDelimiter } from '~/lib/utils/text_utility';
import { visitUrl } from '~/lib/utils/url_utility';
+import Todos from '~/pages/dashboard/todos/index/todos';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrl'),
diff --git a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js
index a91fc5abe09..7a8a249cb2a 100644
--- a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js
+++ b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js
@@ -1,8 +1,8 @@
+import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlButton } from '@gitlab/ui';
-import BitbucketServerStatusTable from '~/pages/import/bitbucket_server/status/components/bitbucket_server_status_table.vue';
import BitbucketStatusTable from '~/import_entities/import_projects/components/bitbucket_status_table.vue';
+import BitbucketServerStatusTable from '~/pages/import/bitbucket_server/status/components/bitbucket_server_status_table.vue';
const BitbucketStatusTableStub = {
name: 'BitbucketStatusTable',
diff --git a/spec/frontend/pages/labels/components/promote_label_modal_spec.js b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
index 19807313c77..4d5d1f98b59 100644
--- a/spec/frontend/pages/labels/components/promote_label_modal_spec.js
+++ b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import axios from '~/lib/utils/axios_utils';
import promoteLabelModal from '~/pages/projects/labels/components/promote_label_modal.vue';
import eventHub from '~/pages/projects/labels/event_hub';
-import axios from '~/lib/utils/axios_utils';
describe('Promote label modal', () => {
let vm;
diff --git a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
index 7bb637356c2..1fbec0d996d 100644
--- a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
+++ b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import { redirectTo } from '~/lib/utils/url_utility';
+import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
+import { redirectTo } from '~/lib/utils/url_utility';
import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub';
diff --git a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
index 7cd94deb3da..d22e0474e06 100644
--- a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
+++ b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -1,12 +1,12 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { TEST_HOST } from 'helpers/test_constants';
import { setHTMLFixture } from 'helpers/fixtures';
+import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
-import PromoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue';
+import * as flash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
-import * as flash from '~/flash';
+import PromoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash');
diff --git a/spec/frontend/pages/projects/edit/mount_search_settings_spec.js b/spec/frontend/pages/projects/edit/mount_search_settings_spec.js
deleted file mode 100644
index b48809b3d00..00000000000
--- a/spec/frontend/pages/projects/edit/mount_search_settings_spec.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import initSearch from '~/search_settings';
-import mountSearchSettings from '~/pages/projects/edit/mount_search_settings';
-
-jest.mock('~/search_settings');
-
-describe('pages/projects/edit/mount_search_settings', () => {
- afterEach(() => {
- resetHTMLFixture();
- });
-
- it('initializes search settings when js-search-settings-app is available', async () => {
- setHTMLFixture('<div class="js-search-settings-app"></div>');
-
- await mountSearchSettings();
-
- expect(initSearch).toHaveBeenCalled();
- });
-
- it('does not initialize search settings when js-search-settings-app is unavailable', async () => {
- await mountSearchSettings();
-
- expect(initSearch).not.toHaveBeenCalled();
- });
-});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
index b90c07a335b..b5425fa6f2e 100644
--- a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
@@ -1,14 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlButton, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
describe('Fork groups list item component', () => {
let wrapper;
- const DEFAULT_PROPS = {
- hasReachedProjectLimit: false,
- };
-
const DEFAULT_GROUP_DATA = {
id: 22,
name: 'Gitlab Org',
@@ -33,7 +29,6 @@ describe('Fork groups list item component', () => {
const createWrapper = (propsData) => {
wrapper = shallowMount(ForkGroupsListItem, {
propsData: {
- ...DEFAULT_PROPS,
...propsData,
},
});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
index 91740c7ce3b..e7ac837a4c8 100644
--- a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
@@ -1,10 +1,10 @@
-import AxiosMockAdapter from 'axios-mock-adapter';
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import ForkGroupsList from '~/pages/projects/forks/new/components/fork_groups_list.vue';
import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
@@ -16,7 +16,6 @@ describe('Fork groups list component', () => {
const DEFAULT_PROPS = {
endpoint: '/dummy',
- hasReachedProjectLimit: false,
};
const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args);
@@ -94,10 +93,9 @@ describe('Fork groups list component', () => {
it('renders list items for each available group', async () => {
const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }];
- const hasReachedProjectLimit = true;
replyWith(200, { namespaces });
- createWrapper({ hasReachedProjectLimit });
+ createWrapper();
await waitForPromises();
@@ -106,7 +104,6 @@ describe('Fork groups list component', () => {
namespaces.forEach((namespace, idx) => {
expect(wrapper.findAll(ForkGroupsListItem).at(idx).props()).toStrictEqual({
group: namespace,
- hasReachedProjectLimit,
});
});
});
diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
index 4a60c7fd509..1f9029b40c7 100644
--- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js
+++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
@@ -1,13 +1,13 @@
-import MockAdapter from 'axios-mock-adapter';
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
import CodeCoverage from '~/pages/projects/graphs/components/code_coverage.vue';
import { codeCoverageMockData, sortedDataByDates } from './mock_data';
-import httpStatusCodes from '~/lib/utils/http_status';
describe('Code Coverage', () => {
let wrapper;
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap
new file mode 100644
index 00000000000..c9141d13a46
--- /dev/null
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Learn GitLab Design A should render the loading state 1`] = `
+<ul>
+ <li>
+ <span>
+ Create a repository
+ </span>
+ </li>
+ <li>
+ <span>
+ Invite your colleagues
+ </span>
+ </li>
+ <li>
+ <span>
+ Set-up CI/CD
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Start a free trial of GitLab Gold
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Add code owners
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Enable require merge approvals
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Submit a merge request (MR)
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Run a Security scan using CI/CD
+ </gl-link-stub>
+ </span>
+ </li>
+</ul>
+`;
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap
new file mode 100644
index 00000000000..85e3b675e5b
--- /dev/null
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Learn GitLab Design B should render the loading state 1`] = `
+<ul>
+ <li>
+ <span>
+ Create a repository
+ </span>
+ </li>
+ <li>
+ <span>
+ Invite your colleagues
+ </span>
+ </li>
+ <li>
+ <span>
+ Set-up CI/CD
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Start a free trial of GitLab Gold
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Add code owners
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Enable require merge approvals
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Submit a merge request (MR)
+ </gl-link-stub>
+ </span>
+ </li>
+ <li>
+ <span>
+ <gl-link-stub
+ href="http://example.com/"
+ >
+ Run a Security scan using CI/CD
+ </gl-link-stub>
+ </span>
+ </li>
+</ul>
+`;
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js
new file mode 100644
index 00000000000..ddc5339e7e0
--- /dev/null
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js
@@ -0,0 +1,63 @@
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue';
+
+const TEST_ACTIONS = {
+ gitWrite: {
+ url: 'http://example.com/',
+ completed: true,
+ },
+ userAdded: {
+ url: 'http://example.com/',
+ completed: true,
+ },
+ pipelineCreated: {
+ url: 'http://example.com/',
+ completed: true,
+ },
+ trialStarted: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ codeOwnersEnabled: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ requiredMrApprovalsEnabled: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ mergeRequestCreated: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ securityScanEnabled: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+};
+
+describe('Learn GitLab Design A', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const createWrapper = () => {
+ wrapper = extendedWrapper(
+ shallowMount(LearnGitlabA, {
+ propsData: {
+ actions: TEST_ACTIONS,
+ },
+ }),
+ );
+ };
+
+ it('should render the loading state', () => {
+ createWrapper();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js
new file mode 100644
index 00000000000..be4f5768402
--- /dev/null
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js
@@ -0,0 +1,63 @@
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue';
+
+const TEST_ACTIONS = {
+ gitWrite: {
+ url: 'http://example.com/',
+ completed: true,
+ },
+ userAdded: {
+ url: 'http://example.com/',
+ completed: true,
+ },
+ pipelineCreated: {
+ url: 'http://example.com/',
+ completed: true,
+ },
+ trialStarted: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ codeOwnersEnabled: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ requiredMrApprovalsEnabled: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ mergeRequestCreated: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+ securityScanEnabled: {
+ url: 'http://example.com/',
+ completed: false,
+ },
+};
+
+describe('Learn GitLab Design B', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const createWrapper = () => {
+ wrapper = extendedWrapper(
+ shallowMount(LearnGitlabA, {
+ propsData: {
+ actions: TEST_ACTIONS,
+ },
+ }),
+ );
+ };
+
+ it('should render the loading state', () => {
+ createWrapper();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js b/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js
new file mode 100644
index 00000000000..ea49111760b
--- /dev/null
+++ b/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js
@@ -0,0 +1,41 @@
+import initCheckFormState from '~/pages/projects/merge_requests/edit/check_form_state';
+
+describe('Check form state', () => {
+ const findInput = () => document.querySelector('#form-input');
+
+ let beforeUnloadEvent;
+ let setDialogContent;
+
+ beforeEach(() => {
+ setFixtures(`
+ <form class="merge-request-form">
+ <input type="text" name="test" id="form-input"/>
+ </form>`);
+
+ beforeUnloadEvent = new Event('beforeunload');
+ jest.spyOn(beforeUnloadEvent, 'preventDefault');
+ setDialogContent = jest.spyOn(beforeUnloadEvent, 'returnValue', 'set');
+
+ initCheckFormState();
+ });
+
+ afterEach(() => {
+ beforeUnloadEvent.preventDefault.mockRestore();
+ setDialogContent.mockRestore();
+ });
+
+ it('shows confirmation dialog when there are unsaved changes', () => {
+ findInput().value = 'value changed';
+ window.dispatchEvent(beforeUnloadEvent);
+
+ expect(beforeUnloadEvent.preventDefault).toHaveBeenCalled();
+ expect(setDialogContent).toHaveBeenCalledWith('');
+ });
+
+ it('does not show confirmation dialog when there are no unsaved changes', () => {
+ window.dispatchEvent(beforeUnloadEvent);
+
+ expect(beforeUnloadEvent.preventDefault).not.toHaveBeenCalled();
+ expect(setDialogContent).not.toHaveBeenCalled();
+ });
+});
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 cfe54016410..5fed9fcaad2 100644
--- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
import PipelineSchedulesCallout from '~/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue';
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 c90ebd47b08..0fffcf433a3 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
@@ -1,7 +1,6 @@
-import { mount, shallowMount } from '@vue/test-utils';
-
-import projectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
-import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue';
+import { GlToggle } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import ProjectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
describe('Project Feature Settings', () => {
const defaultProps = {
@@ -19,81 +18,76 @@ describe('Project Feature Settings', () => {
};
let wrapper;
- const mountComponent = (customProps) => {
- const propsData = { ...defaultProps, ...customProps };
- return shallowMount(projectFeatureSetting, { propsData });
- };
+ const findHiddenInput = () => wrapper.find(`input[name=${defaultProps.name}]`);
+ const findToggle = () => wrapper.findComponent(GlToggle);
- beforeEach(() => {
- wrapper = mountComponent();
- });
+ const mountComponent = (customProps = {}) =>
+ shallowMount(ProjectFeatureSetting, {
+ propsData: {
+ ...defaultProps,
+ ...customProps,
+ },
+ });
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
describe('Hidden name input', () => {
it('should set the hidden name input if the name exists', () => {
- expect(wrapper.find(`input[name=${defaultProps.name}]`).attributes().value).toBe('1');
+ wrapper = mountComponent();
+
+ expect(findHiddenInput().attributes('value')).toBe('1');
});
it('should not set the hidden name input if the name does not exist', () => {
- wrapper.setProps({ name: null });
+ wrapper = mountComponent({ name: null });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(`input[name=${defaultProps.name}]`).exists()).toBe(false);
- });
+ expect(findHiddenInput().exists()).toBe(false);
});
});
describe('Feature toggle', () => {
- it('should be hidden if "showToggle" is passed false', async () => {
- wrapper.setProps({ showToggle: false });
-
- await wrapper.vm.$nextTick();
+ it('should be hidden if "showToggle" is passed false', () => {
+ wrapper = mountComponent({ showToggle: false });
- expect(wrapper.find(projectFeatureToggle).element).toBeUndefined();
+ expect(findToggle().exists()).toBe(false);
});
it('should enable the feature toggle if the value is not 0', () => {
- expect(wrapper.find(projectFeatureToggle).props().value).toBe(true);
+ wrapper = mountComponent();
+
+ expect(findToggle().props('value')).toBe(true);
});
it('should enable the feature toggle if the value is less than 0', () => {
- wrapper.setProps({ value: -1 });
+ wrapper = mountComponent({ value: -1 });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(projectFeatureToggle).props().value).toBe(true);
- });
+ expect(findToggle().props('value')).toBe(true);
});
it('should disable the feature toggle if the value is 0', () => {
- wrapper.setProps({ value: 0 });
+ wrapper = mountComponent({ value: 0 });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(projectFeatureToggle).props().value).toBe(false);
- });
+ expect(findToggle().props('value')).toBe(false);
});
it('should disable the feature toggle if disabledInput is set', () => {
- wrapper.setProps({ disabledInput: true });
+ wrapper = mountComponent({ disabledInput: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(projectFeatureToggle).props().disabledInput).toBe(true);
- });
+ expect(findToggle().props('disabled')).toBe(true);
});
it('should emit a change event when the feature toggle changes', () => {
- // Needs to be fully mounted to be able to trigger the click event on the internal button
- wrapper = mount(projectFeatureSetting, { propsData: defaultProps });
+ wrapper = mountComponent({ propsData: defaultProps });
+
+ expect(wrapper.emitted('change')).toBeUndefined();
- expect(wrapper.emitted().change).toBeUndefined();
- wrapper.find(projectFeatureToggle).find('button').trigger('click');
+ findToggle().vm.$emit('change', false);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().change.length).toBe(1);
- expect(wrapper.emitted().change[0]).toEqual([0]);
- });
+ expect(wrapper.emitted('change')).toHaveLength(1);
+ expect(wrapper.emitted('change')[0]).toEqual([0]);
});
});
@@ -108,26 +102,23 @@ describe('Project Feature Settings', () => {
`(
'should set disabled to $isDisabled when disabledInput is $disabledInput, the value is $value and options are $options',
({ disabledInput, value, options, isDisabled }) => {
- wrapper.setProps({ disabledInput, value, options });
-
- return wrapper.vm.$nextTick(() => {
- if (isDisabled) {
- expect(wrapper.find('select').attributes().disabled).toEqual('disabled');
- } else {
- expect(wrapper.find('select').attributes().disabled).toBeUndefined();
- }
- });
+ wrapper = mountComponent({ disabledInput, value, options });
+
+ const expected = isDisabled ? 'disabled' : undefined;
+
+ expect(wrapper.find('select').attributes('disabled')).toBe(expected);
},
);
it('should emit the change when a new option is selected', () => {
- expect(wrapper.emitted().change).toBeUndefined();
+ wrapper = mountComponent();
+
+ expect(wrapper.emitted('change')).toBeUndefined();
+
wrapper.findAll('option').at(1).trigger('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().change.length).toBe(1);
- expect(wrapper.emitted().change[0]).toEqual([2]);
- });
+ expect(wrapper.emitted('change')).toHaveLength(1);
+ expect(wrapper.emitted('change')[0]).toEqual([2]);
});
});
});
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 9aee6ec7ace..d7c754fd3cc 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
@@ -1,13 +1,12 @@
+import { GlToggle } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
-
+import projectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
import settingsPanel from '~/pages/projects/shared/permissions/components/settings_panel.vue';
import {
featureAccessLevel,
visibilityLevelDescriptions,
visibilityOptions,
} from '~/pages/projects/shared/permissions/constants';
-import projectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
-import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue';
const defaultProps = {
currentSettings: {
@@ -69,57 +68,61 @@ describe('Settings Panel', () => {
});
};
- const overrideCurrentSettings = (
- currentSettingsProps,
- extraProps = {},
- mountFn = shallowMount,
- ) => {
- return mountComponent({ ...extraProps, currentSettings: currentSettingsProps }, mountFn);
- };
-
const findLFSSettingsRow = () => wrapper.find({ ref: 'git-lfs-settings' });
const findLFSSettingsMessage = () => findLFSSettingsRow().find('p');
- const findLFSFeatureToggle = () => findLFSSettingsRow().find(projectFeatureToggle);
-
+ const findLFSFeatureToggle = () => findLFSSettingsRow().find(GlToggle);
const findRepositoryFeatureProjectRow = () => wrapper.find({ ref: 'repository-settings' });
const findRepositoryFeatureSetting = () =>
findRepositoryFeatureProjectRow().find(projectFeatureSetting);
-
+ const findProjectVisibilitySettings = () => wrapper.find({ ref: 'project-visibility-settings' });
const findAnalyticsRow = () => wrapper.find({ ref: 'analytics-settings' });
-
- beforeEach(() => {
- wrapper = mountComponent();
- });
+ const findProjectVisibilityLevelInput = () => wrapper.find('[name="project[visibility_level]"]');
+ const findRequestAccessEnabledInput = () =>
+ wrapper.find('[name="project[request_access_enabled]"]');
+ const findMergeRequestsAccessLevelInput = () =>
+ wrapper.find('[name="project[project_feature_attributes][merge_requests_access_level]"]');
+ const findForkingAccessLevelInput = () =>
+ wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]');
+ const findBuildsAccessLevelInput = () =>
+ wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]');
+ const findContainerRegistrySettings = () => wrapper.find({ ref: 'container-registry-settings' });
+ const findContainerRegistryEnabledInput = () =>
+ wrapper.find('[name="project[container_registry_enabled]"]');
+ const findPackageSettings = () => wrapper.find({ ref: 'package-settings' });
+ const findPackagesEnabledInput = () => wrapper.find('[name="project[packages_enabled]"]');
+ const findPagesSettings = () => wrapper.find({ ref: 'pages-settings' });
+ const findEmailSettings = () => wrapper.find({ ref: 'email-settings' });
+ const findShowDefaultAwardEmojis = () =>
+ wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
+ const findMetricsVisibilitySettings = () => wrapper.find({ ref: 'metrics-visibility-settings' });
+ const findAllowEditingCommitMessages = () =>
+ wrapper.find({ ref: 'allow-editing-commit-messages' }).exists();
+ const findOperationsSettings = () => wrapper.find({ ref: 'operations-settings' });
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
describe('Project Visibility', () => {
it('should set the project visibility help path', () => {
- expect(wrapper.find({ ref: 'project-visibility-settings' }).props().helpPath).toBe(
+ wrapper = mountComponent();
+
+ expect(findProjectVisibilitySettings().props('helpPath')).toBe(
defaultProps.visibilityHelpPath,
);
});
it('should not disable the visibility level dropdown', () => {
- wrapper.setProps({ canChangeVisibilityLevel: true });
+ wrapper = mountComponent({ canChangeVisibilityLevel: true });
- return wrapper.vm.$nextTick(() => {
- expect(
- wrapper.find('[name="project[visibility_level]"]').attributes().disabled,
- ).toBeUndefined();
- });
+ expect(findProjectVisibilityLevelInput().attributes('disabled')).toBeUndefined();
});
it('should disable the visibility level dropdown', () => {
- wrapper.setProps({ canChangeVisibilityLevel: false });
+ wrapper = mountComponent({ canChangeVisibilityLevel: false });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('[name="project[visibility_level]"]').attributes().disabled).toBe(
- 'disabled',
- );
- });
+ expect(findProjectVisibilityLevelInput().attributes('disabled')).toBe('disabled');
});
it.each`
@@ -133,232 +136,209 @@ describe('Settings Panel', () => {
`(
'sets disabled to $disabled for the visibility option $option when given $allowedOptions',
({ option, allowedOptions, disabled }) => {
- wrapper.setProps({ allowedVisibilityOptions: allowedOptions });
-
- return wrapper.vm.$nextTick(() => {
- const attributeValue = wrapper
- .find(`[name="project[visibility_level]"] option[value="${option}"]`)
- .attributes().disabled;
-
- if (disabled) {
- expect(attributeValue).toBe('disabled');
- } else {
- expect(attributeValue).toBeUndefined();
- }
- });
+ wrapper = mountComponent({ allowedVisibilityOptions: allowedOptions });
+
+ const attributeValue = findProjectVisibilityLevelInput()
+ .find(`option[value="${option}"]`)
+ .attributes('disabled');
+
+ const expected = disabled ? 'disabled' : undefined;
+
+ expect(attributeValue).toBe(expected);
},
);
it('should set the visibility level description based upon the selected visibility level', () => {
- wrapper.find('[name="project[visibility_level]"]').setValue(visibilityOptions.INTERNAL);
+ wrapper = mountComponent();
+
+ findProjectVisibilityLevelInput().setValue(visibilityOptions.INTERNAL);
- expect(wrapper.find({ ref: 'project-visibility-settings' }).text()).toContain(
+ expect(findProjectVisibilitySettings().text()).toContain(
visibilityLevelDescriptions[visibilityOptions.INTERNAL],
);
});
it('should show the request access checkbox if the visibility level is not private', () => {
- wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.INTERNAL });
+ wrapper = mountComponent({
+ currentSettings: { visibilityLevel: visibilityOptions.INTERNAL },
+ });
- expect(wrapper.find('[name="project[request_access_enabled]"]').exists()).toBe(true);
+ expect(findRequestAccessEnabledInput().exists()).toBe(true);
});
it('should not show the request access checkbox if the visibility level is private', () => {
- wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
+ wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } });
- expect(wrapper.find('[name="project[request_access_enabled]"]').exists()).toBe(false);
+ expect(findRequestAccessEnabledInput().exists()).toBe(false);
});
});
describe('Repository', () => {
it('should set the repository help text when the visibility level is set to private', () => {
- wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
+ wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } });
- expect(findRepositoryFeatureProjectRow().props().helpText).toBe(
- 'View and edit files in this project',
+ expect(findRepositoryFeatureProjectRow().props('helpText')).toBe(
+ 'View and edit files in this project.',
);
});
it('should set the repository help text with a read access warning when the visibility level is set to non-private', () => {
- wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PUBLIC });
+ wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC } });
- expect(findRepositoryFeatureProjectRow().props().helpText).toBe(
- 'View and edit files in this project. Non-project members will only have read access',
+ expect(findRepositoryFeatureProjectRow().props('helpText')).toBe(
+ 'View and edit files in this project. Non-project members will only have read access.',
);
});
});
describe('Merge requests', () => {
it('should enable the merge requests access level input when the repository is enabled', () => {
- wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ });
- expect(
- wrapper
- .find('[name="project[project_feature_attributes][merge_requests_access_level]"]')
- .props().disabledInput,
- ).toBe(false);
+ expect(findMergeRequestsAccessLevelInput().props('disabledInput')).toBe(false);
});
it('should disable the merge requests access level input when the repository is disabled', () => {
- wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ });
- expect(
- wrapper
- .find('[name="project[project_feature_attributes][merge_requests_access_level]"]')
- .props().disabledInput,
- ).toBe(true);
+ expect(findMergeRequestsAccessLevelInput().props('disabledInput')).toBe(true);
});
});
describe('Forks', () => {
it('should enable the forking access level input when the repository is enabled', () => {
- wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ });
- expect(
- wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]').props()
- .disabledInput,
- ).toBe(false);
+ expect(findForkingAccessLevelInput().props('disabledInput')).toBe(false);
});
it('should disable the forking access level input when the repository is disabled', () => {
- wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ });
- expect(
- wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]').props()
- .disabledInput,
- ).toBe(true);
+ expect(findForkingAccessLevelInput().props('disabledInput')).toBe(true);
});
});
describe('Pipelines', () => {
it('should enable the builds access level input when the repository is enabled', () => {
- wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ });
- expect(
- wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]').props()
- .disabledInput,
- ).toBe(false);
+ expect(findBuildsAccessLevelInput().props('disabledInput')).toBe(false);
});
it('should disable the builds access level input when the repository is disabled', () => {
- wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ });
- expect(
- wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]').props()
- .disabledInput,
- ).toBe(true);
+ expect(findBuildsAccessLevelInput().props('disabledInput')).toBe(true);
});
});
describe('Container registry', () => {
it('should show the container registry settings if the registry is available', () => {
- wrapper.setProps({ registryAvailable: true });
+ wrapper = mountComponent({ registryAvailable: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'container-registry-settings' }).exists()).toBe(true);
- });
+ expect(findContainerRegistrySettings().exists()).toBe(true);
});
it('should hide the container registry settings if the registry is not available', () => {
- wrapper.setProps({ registryAvailable: false });
+ wrapper = mountComponent({ registryAvailable: false });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'container-registry-settings' }).exists()).toBe(false);
- });
+ expect(findContainerRegistrySettings().exists()).toBe(false);
});
it('should set the container registry settings help path', () => {
- wrapper.setProps({ registryAvailable: true });
+ wrapper = mountComponent({ registryAvailable: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'container-registry-settings' }).props().helpPath).toBe(
- defaultProps.registryHelpPath,
- );
- });
+ expect(findContainerRegistrySettings().props('helpPath')).toBe(defaultProps.registryHelpPath);
});
it('should show the container registry public note if the visibility level is public and the registry is available', () => {
- wrapper = overrideCurrentSettings(
- { visibilityLevel: visibilityOptions.PUBLIC },
- { registryAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC },
+ registryAvailable: true,
+ });
- expect(wrapper.find({ ref: 'container-registry-settings' }).text()).toContain(
+ expect(findContainerRegistrySettings().text()).toContain(
'Note: the container registry is always visible when a project is public',
);
});
it('should hide the container registry public note if the visibility level is private and the registry is available', () => {
- wrapper = overrideCurrentSettings(
- { visibilityLevel: visibilityOptions.PRIVATE },
- { registryAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE },
+ registryAvailable: true,
+ });
- expect(wrapper.find({ ref: 'container-registry-settings' }).text()).not.toContain(
+ expect(findContainerRegistrySettings().text()).not.toContain(
'Note: the container registry is always visible when a project is public',
);
});
it('should enable the container registry input when the repository is enabled', () => {
- wrapper = overrideCurrentSettings(
- { repositoryAccessLevel: featureAccessLevel.EVERYONE },
- { registryAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ registryAvailable: true,
+ });
- expect(
- wrapper.find('[name="project[container_registry_enabled]"]').props().disabledInput,
- ).toBe(false);
+ expect(findContainerRegistryEnabledInput().props('disabled')).toBe(false);
});
it('should disable the container registry input when the repository is disabled', () => {
- wrapper = overrideCurrentSettings(
- { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
- { registryAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ registryAvailable: true,
+ });
- expect(
- wrapper.find('[name="project[container_registry_enabled]"]').props().disabledInput,
- ).toBe(true);
+ expect(findContainerRegistryEnabledInput().props('disabled')).toBe(true);
});
});
describe('Git Large File Storage', () => {
it('should show the LFS settings if LFS is available', () => {
- wrapper.setProps({ lfsAvailable: true });
+ wrapper = mountComponent({ lfsAvailable: true });
- return wrapper.vm.$nextTick(() => {
- expect(findLFSSettingsRow().exists()).toBe(true);
- });
+ expect(findLFSSettingsRow().exists()).toBe(true);
});
it('should hide the LFS settings if LFS is not available', () => {
- wrapper.setProps({ lfsAvailable: false });
+ wrapper = mountComponent({ lfsAvailable: false });
- return wrapper.vm.$nextTick(() => {
- expect(findLFSSettingsRow().exists()).toBe(false);
- });
+ expect(findLFSSettingsRow().exists()).toBe(false);
});
it('should set the LFS settings help path', () => {
- expect(findLFSSettingsRow().props().helpPath).toBe(defaultProps.lfsHelpPath);
+ wrapper = mountComponent();
+ expect(findLFSSettingsRow().props('helpPath')).toBe(defaultProps.lfsHelpPath);
});
it('should enable the LFS input when the repository is enabled', () => {
- wrapper = overrideCurrentSettings(
- { repositoryAccessLevel: featureAccessLevel.EVERYONE },
- { lfsAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ lfsAvailable: true,
+ });
- expect(findLFSFeatureToggle().props().disabledInput).toBe(false);
+ expect(findLFSFeatureToggle().props('disabled')).toBe(false);
});
it('should disable the LFS input when the repository is disabled', () => {
- wrapper = overrideCurrentSettings(
- { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
- { lfsAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ lfsAvailable: true,
+ });
- expect(findLFSFeatureToggle().props().disabledInput).toBe(true);
+ expect(findLFSFeatureToggle().props('disabled')).toBe(true);
});
it('should not change lfsEnabled when disabling the repository', async () => {
@@ -373,8 +353,7 @@ describe('Settings Panel', () => {
expect(isToggleButtonChecked(lfsFeatureToggleButton)).toBe(true);
expect(isToggleButtonChecked(repositoryFeatureToggleButton)).toBe(true);
- repositoryFeatureToggleButton.trigger('click');
- await wrapper.vm.$nextTick();
+ await repositoryFeatureToggleButton.trigger('click');
expect(isToggleButtonChecked(repositoryFeatureToggleButton)).toBe(false);
// LFS toggle should still be checked
@@ -400,7 +379,7 @@ describe('Settings Panel', () => {
const link = message.find('a');
expect(message.text()).toContain(
- 'LFS objects from this repository are still available to forks',
+ 'LFS objects from this repository are available to forks.',
);
expect(link.text()).toBe('How do I remove them?');
expect(link.attributes('href')).toBe(
@@ -418,47 +397,39 @@ describe('Settings Panel', () => {
describe('Packages', () => {
it('should show the packages settings if packages are available', () => {
- wrapper.setProps({ packagesAvailable: true });
+ wrapper = mountComponent({ packagesAvailable: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'package-settings' }).exists()).toBe(true);
- });
+ expect(findPackageSettings().exists()).toBe(true);
});
it('should hide the packages settings if packages are not available', () => {
- wrapper.setProps({ packagesAvailable: false });
+ wrapper = mountComponent({ packagesAvailable: false });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'package-settings' }).exists()).toBe(false);
- });
+ expect(findPackageSettings().exists()).toBe(false);
});
it('should set the package settings help path', () => {
- wrapper.setProps({ packagesAvailable: true });
+ wrapper = mountComponent({ packagesAvailable: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'package-settings' }).props().helpPath).toBe(
- defaultProps.packagesHelpPath,
- );
- });
+ expect(findPackageSettings().props('helpPath')).toBe(defaultProps.packagesHelpPath);
});
it('should enable the packages input when the repository is enabled', () => {
- wrapper = overrideCurrentSettings(
- { repositoryAccessLevel: featureAccessLevel.EVERYONE },
- { packagesAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ packagesAvailable: true,
+ });
- expect(wrapper.find('[name="project[packages_enabled]"]').props().disabledInput).toBe(false);
+ expect(findPackagesEnabledInput().props('disabled')).toBe(false);
});
it('should disable the packages input when the repository is disabled', () => {
- wrapper = overrideCurrentSettings(
- { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
- { packagesAvailable: true },
- );
+ wrapper = mountComponent({
+ currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ packagesAvailable: true,
+ });
- expect(wrapper.find('[name="project[packages_enabled]"]').props().disabledInput).toBe(true);
+ expect(findPackagesEnabledInput().props('disabled')).toBe(true);
});
});
@@ -472,78 +443,66 @@ describe('Settings Panel', () => {
`(
'should $visibility the page settings if pagesAvailable is $pagesAvailable and pagesAccessControlEnabled is $pagesAccessControlEnabled',
({ pagesAvailable, pagesAccessControlEnabled, visibility }) => {
- wrapper.setProps({ pagesAvailable, pagesAccessControlEnabled });
+ wrapper = mountComponent({ pagesAvailable, pagesAccessControlEnabled });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'pages-settings' }).exists()).toBe(visibility === 'show');
- });
+ expect(findPagesSettings().exists()).toBe(visibility === 'show');
},
);
it('should set the pages settings help path', () => {
- wrapper.setProps({ pagesAvailable: true, pagesAccessControlEnabled: true });
+ wrapper = mountComponent({ pagesAvailable: true, pagesAccessControlEnabled: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'pages-settings' }).props().helpPath).toBe(
- defaultProps.pagesHelpPath,
- );
- });
+ expect(findPagesSettings().props('helpPath')).toBe(defaultProps.pagesHelpPath);
});
});
describe('Email notifications', () => {
it('should show the disable email notifications input if emails an be disabled', () => {
- wrapper.setProps({ canDisableEmails: true });
+ wrapper = mountComponent({ canDisableEmails: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'email-settings' }).exists()).toBe(true);
- });
+ expect(findEmailSettings().exists()).toBe(true);
});
it('should hide the disable email notifications input if emails cannot be disabled', () => {
- wrapper.setProps({ canDisableEmails: false });
+ wrapper = mountComponent({ canDisableEmails: false });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'email-settings' }).exists()).toBe(false);
- });
+ expect(findEmailSettings().exists()).toBe(false);
});
});
describe('Default award emojis', () => {
it('should show the "Show default award emojis" input', () => {
- return wrapper.vm.$nextTick(() => {
- expect(
- wrapper
- .find('input[name="project[project_setting_attributes][show_default_award_emojis]"]')
- .exists(),
- ).toBe(true);
- });
+ wrapper = mountComponent();
+
+ expect(findShowDefaultAwardEmojis().exists()).toBe(true);
});
});
describe('Metrics dashboard', () => {
it('should show the metrics dashboard access toggle', () => {
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find({ ref: 'metrics-visibility-settings' }).exists()).toBe(true);
- });
+ wrapper = mountComponent();
+
+ expect(findMetricsVisibilitySettings().exists()).toBe(true);
});
it('should contain help text', () => {
- expect(wrapper.find({ ref: 'metrics-visibility-settings' }).props().helpText).toBe(
- 'With Metrics Dashboard you can visualize this project performance metrics',
+ wrapper = mountComponent();
+
+ expect(findMetricsVisibilitySettings().props('helpText')).toBe(
+ "Visualize the project's performance metrics.",
);
});
it.each`
- scenario | selectedOption | selectedOptionLabel
- ${{ visibilityLevel: visibilityOptions.PRIVATE }} | ${String(featureAccessLevel.PROJECT_MEMBERS)} | ${'Only Project Members'}
- ${{ operationsAccessLevel: featureAccessLevel.NOT_ENABLED }} | ${String(featureAccessLevel.NOT_ENABLED)} | ${'Enable feature to choose access level'}
+ scenario | selectedOption | selectedOptionLabel
+ ${{ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } }} | ${String(featureAccessLevel.PROJECT_MEMBERS)} | ${'Only Project Members'}
+ ${{ currentSettings: { operationsAccessLevel: featureAccessLevel.NOT_ENABLED } }} | ${String(featureAccessLevel.NOT_ENABLED)} | ${'Enable feature to choose access level'}
`(
'should disable the metrics visibility dropdown when #scenario',
({ scenario, selectedOption, selectedOptionLabel }) => {
- wrapper = overrideCurrentSettings(scenario, {}, mount);
+ wrapper = mountComponent(scenario, mount);
- const select = wrapper.find({ ref: 'metrics-visibility-settings' }).find('select');
+ const select = findMetricsVisibilitySettings().find('select');
const option = select.find('option');
expect(select.attributes('disabled')).toBe('disabled');
@@ -556,31 +515,29 @@ describe('Settings Panel', () => {
describe('Settings panel with feature flags', () => {
describe('Allow edit of commit message', () => {
- it('should show the allow editing of commit messages checkbox', async () => {
+ it('should show the allow editing of commit messages checkbox', () => {
wrapper = mountComponent({
glFeatures: { allowEditingCommitMessages: true },
});
- await wrapper.vm.$nextTick();
-
- expect(wrapper.find({ ref: 'allow-editing-commit-messages' }).exists()).toBe(true);
+ expect(findAllowEditingCommitMessages()).toBe(true);
});
});
});
describe('Analytics', () => {
- it('should show the analytics toggle', async () => {
- await wrapper.vm.$nextTick();
+ it('should show the analytics toggle', () => {
+ wrapper = mountComponent();
expect(findAnalyticsRow().exists()).toBe(true);
});
});
describe('Operations', () => {
- it('should show the operations toggle', async () => {
- await wrapper.vm.$nextTick();
+ it('should show the operations toggle', () => {
+ wrapper = mountComponent();
- expect(wrapper.find({ ref: 'operations-settings' }).exists()).toBe(true);
+ expect(findOperationsSettings().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/performance_bar/components/detailed_metric_spec.js b/spec/frontend/performance_bar/components/detailed_metric_spec.js
index 754ffcc12e3..6ddd047d549 100644
--- a/spec/frontend/performance_bar/components/detailed_metric_spec.js
+++ b/spec/frontend/performance_bar/components/detailed_metric_spec.js
@@ -1,5 +1,5 @@
-import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import DetailedMetric from '~/performance_bar/components/detailed_metric.vue';
import RequestWarning from '~/performance_bar/components/request_warning.vue';
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index 578fd8d836a..403142d7ff7 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
+import { deprecatedCreateFlash as Flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import PersistentUserCallout from '~/persistent_user_callout';
-import { deprecatedCreateFlash as Flash } from '~/flash';
jest.mock('~/flash');
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
index aae25a3aa6d..5dae77a4626 100644
--- a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
+++ b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlFormInput, GlFormTextarea } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import { mockCommitMessage, mockDefaultBranch } from '../../mock_data';
-describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
+describe('Pipeline Editor | Commit Form', () => {
let wrapper;
const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
@@ -21,8 +21,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
});
};
- const findCommitTextarea = () => wrapper.find(GlFormTextarea);
- const findBranchInput = () => wrapper.find(GlFormInput);
+ const findCommitTextarea = () => wrapper.findComponent(GlFormTextarea);
+ const findBranchInput = () => wrapper.findComponent(GlFormInput);
const findNewMrCheckbox = () => wrapper.find('[data-testid="new-mr-checkbox"]');
const findSubmitBtn = () => wrapper.find('[type="submit"]');
const findCancelBtn = () => wrapper.find('[type="reset"]');
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
new file mode 100644
index 00000000000..b87ff6ec0de
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
@@ -0,0 +1,223 @@
+import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
+import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
+import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
+import { COMMIT_SUCCESS } from '~/pipeline_editor/constants';
+import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
+
+import {
+ mockCiConfigPath,
+ mockCiYml,
+ mockCommitSha,
+ mockCommitNextSha,
+ mockCommitMessage,
+ mockDefaultBranch,
+ mockProjectFullPath,
+ mockNewMergeRequestPath,
+} from '../../mock_data';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+ refreshCurrentPage: jest.fn(),
+ objectToQuery: jest.requireActual('~/lib/utils/url_utility').objectToQuery,
+ mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
+}));
+
+const mockVariables = {
+ projectPath: mockProjectFullPath,
+ startBranch: mockDefaultBranch,
+ message: mockCommitMessage,
+ filePath: mockCiConfigPath,
+ content: mockCiYml,
+ lastCommitId: mockCommitSha,
+};
+
+const mockProvide = {
+ ciConfigPath: mockCiConfigPath,
+ defaultBranch: mockDefaultBranch,
+ projectFullPath: mockProjectFullPath,
+ newMergeRequestPath: mockNewMergeRequestPath,
+};
+
+describe('Pipeline Editor | Commit section', () => {
+ let wrapper;
+ let mockMutate;
+
+ const defaultProps = { ciFileContent: mockCiYml };
+
+ const createComponent = ({ props = {}, options = {}, provide = {} } = {}) => {
+ mockMutate = jest.fn().mockResolvedValue({
+ data: {
+ commitCreate: {
+ errors: [],
+ commit: {
+ sha: mockCommitNextSha,
+ },
+ },
+ },
+ });
+
+ wrapper = mount(CommitSection, {
+ propsData: { ...defaultProps, ...props },
+ provide: { ...mockProvide, ...provide },
+ data() {
+ return {
+ commitSha: mockCommitSha,
+ };
+ },
+ mocks: {
+ $apollo: {
+ mutate: mockMutate,
+ },
+ },
+ attachTo: document.body,
+ ...options,
+ });
+ };
+
+ const findCommitForm = () => wrapper.findComponent(CommitForm);
+ const findCommitBtnLoadingIcon = () =>
+ wrapper.find('[type="submit"]').findComponent(GlLoadingIcon);
+
+ const submitCommit = async ({
+ message = mockCommitMessage,
+ branch = mockDefaultBranch,
+ openMergeRequest = false,
+ } = {}) => {
+ await findCommitForm().findComponent(GlFormTextarea).setValue(message);
+ await findCommitForm().findComponent(GlFormInput).setValue(branch);
+ if (openMergeRequest) {
+ await findCommitForm().find('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest);
+ }
+ await findCommitForm().find('[type="submit"]').trigger('click');
+ // Simulate the write to local cache that occurs after a commit
+ await wrapper.setData({ commitSha: mockCommitNextSha });
+ };
+
+ const cancelCommitForm = async () => {
+ const findCancelBtn = () => wrapper.find('[type="reset"]');
+ await findCancelBtn().trigger('click');
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ mockMutate.mockReset();
+
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when the user commits changes to the current branch', () => {
+ beforeEach(async () => {
+ await submitCommit();
+ });
+
+ it('calls the mutation with the default branch', () => {
+ expect(mockMutate).toHaveBeenCalledTimes(1);
+ expect(mockMutate).toHaveBeenCalledWith({
+ mutation: commitCreate,
+ update: expect.any(Function),
+ variables: {
+ ...mockVariables,
+ branch: mockDefaultBranch,
+ },
+ });
+ });
+
+ it('emits an event to communicate the commit was successful', () => {
+ expect(wrapper.emitted('commit')).toHaveLength(1);
+ expect(wrapper.emitted('commit')[0]).toEqual([{ type: COMMIT_SUCCESS }]);
+ });
+
+ it('shows no saving state', () => {
+ expect(findCommitBtnLoadingIcon().exists()).toBe(false);
+ });
+
+ it('a second commit submits the latest sha, keeping the form updated', async () => {
+ await submitCommit();
+
+ expect(mockMutate).toHaveBeenCalledTimes(2);
+ expect(mockMutate).toHaveBeenCalledWith({
+ mutation: commitCreate,
+ update: expect.any(Function),
+ variables: {
+ ...mockVariables,
+ lastCommitId: mockCommitNextSha,
+ branch: mockDefaultBranch,
+ },
+ });
+ });
+ });
+
+ describe('when the user commits changes to a new branch', () => {
+ const newBranch = 'new-branch';
+
+ beforeEach(async () => {
+ await submitCommit({
+ branch: newBranch,
+ });
+ });
+
+ it('calls the mutation with the new branch', () => {
+ expect(mockMutate).toHaveBeenCalledWith({
+ mutation: commitCreate,
+ update: expect.any(Function),
+ variables: {
+ ...mockVariables,
+ branch: newBranch,
+ },
+ });
+ });
+ });
+
+ describe('when the user commits changes to open a new merge request', () => {
+ const newBranch = 'new-branch';
+
+ beforeEach(async () => {
+ await submitCommit({
+ branch: newBranch,
+ openMergeRequest: true,
+ });
+ });
+
+ it('redirects to the merge request page with source and target branches', () => {
+ const branchesQuery = objectToQuery({
+ 'merge_request[source_branch]': newBranch,
+ 'merge_request[target_branch]': mockDefaultBranch,
+ });
+
+ expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`);
+ });
+ });
+
+ describe('when the commit is ocurring', () => {
+ it('shows a saving state', async () => {
+ mockMutate.mockImplementationOnce(() => {
+ expect(findCommitBtnLoadingIcon().exists()).toBe(true);
+ return Promise.resolve();
+ });
+
+ await submitCommit({
+ message: mockCommitMessage,
+ branch: mockDefaultBranch,
+ openMergeRequest: false,
+ });
+ });
+ });
+
+ describe('when the commit form is cancelled', () => {
+ beforeEach(async () => {
+ createComponent();
+ });
+
+ it('emits an event so that it cab be reseted', async () => {
+ await cancelCommitForm();
+
+ expect(wrapper.emitted('resetContent')).toHaveLength(1);
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
new file mode 100644
index 00000000000..866069f337b
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
@@ -0,0 +1,88 @@
+import { GlAlert, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+
+import { EDITOR_READY_EVENT } from '~/editor/constants';
+import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
+import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
+import { INVALID_CI_CONFIG } from '~/pipelines/constants';
+import { mockLintResponse, mockCiConfigPath } from '../../mock_data';
+
+describe('Text editor component', () => {
+ let wrapper;
+
+ const MockEditorLite = {
+ template: '<div/>',
+ props: ['value', 'fileName', 'editorOptions'],
+ mounted() {
+ this.$emit(EDITOR_READY_EVENT);
+ },
+ };
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMount(CiConfigMergedPreview, {
+ propsData: {
+ ciConfigData: mockLintResponse,
+ ...props,
+ },
+ provide: {
+ ciConfigPath: mockCiConfigPath,
+ },
+ stubs: {
+ EditorLite: MockEditorLite,
+ },
+ });
+ };
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findIcon = () => wrapper.findComponent(GlIcon);
+ const findEditor = () => wrapper.findComponent(MockEditorLite);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when status is invalid', () => {
+ beforeEach(() => {
+ createComponent({ props: { ciConfigData: { status: CI_CONFIG_STATUS_INVALID } } });
+ });
+
+ it('show an error message', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[INVALID_CI_CONFIG]);
+ });
+
+ it('hides the editor', () => {
+ expect(findEditor().exists()).toBe(false);
+ });
+ });
+
+ describe('when status is valid', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows an information message that the section is not editable', () => {
+ expect(findIcon().exists()).toBe(true);
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.viewOnlyMessage);
+ });
+
+ it('contains an editor', () => {
+ expect(findEditor().exists()).toBe(true);
+ });
+
+ it('editor contains the value provided', () => {
+ expect(findEditor().props('value')).toBe(mockLintResponse.mergedYaml);
+ });
+
+ it('editor is configured for the CI config path', () => {
+ expect(findEditor().props('fileName')).toBe(mockCiConfigPath);
+ });
+
+ it('editor is readonly', () => {
+ expect(findEditor().props('editorOptions')).toMatchObject({
+ readOnly: true,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js
new file mode 100644
index 00000000000..3bf5a291c69
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js
@@ -0,0 +1,120 @@
+import { shallowMount } from '@vue/test-utils';
+
+import { EDITOR_READY_EVENT } from '~/editor/constants';
+import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
+import {
+ mockCiConfigPath,
+ mockCiYml,
+ mockCommitSha,
+ mockProjectPath,
+ mockProjectNamespace,
+} from '../../mock_data';
+
+describe('Pipeline Editor | Text editor component', () => {
+ let wrapper;
+
+ let editorReadyListener;
+ let mockUse;
+ let mockRegisterCiSchema;
+
+ const MockEditorLite = {
+ template: '<div/>',
+ props: ['value', 'fileName'],
+ mounted() {
+ this.$emit(EDITOR_READY_EVENT);
+ },
+ methods: {
+ getEditor: () => ({
+ use: mockUse,
+ registerCiSchema: mockRegisterCiSchema,
+ }),
+ },
+ };
+
+ const createComponent = (opts = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(TextEditor, {
+ provide: {
+ projectPath: mockProjectPath,
+ projectNamespace: mockProjectNamespace,
+ ciConfigPath: mockCiConfigPath,
+ },
+ attrs: {
+ value: mockCiYml,
+ },
+ // Simulate graphQL client query result
+ data() {
+ return {
+ commitSha: mockCommitSha,
+ };
+ },
+ listeners: {
+ [EDITOR_READY_EVENT]: editorReadyListener,
+ },
+ stubs: {
+ EditorLite: MockEditorLite,
+ },
+ ...opts,
+ });
+ };
+
+ const findEditor = () => wrapper.findComponent(MockEditorLite);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+
+ mockUse.mockClear();
+ mockRegisterCiSchema.mockClear();
+ });
+
+ describe('template', () => {
+ beforeEach(() => {
+ editorReadyListener = jest.fn();
+ mockUse = jest.fn();
+ mockRegisterCiSchema = jest.fn();
+
+ createComponent();
+ });
+
+ it('contains an editor', () => {
+ expect(findEditor().exists()).toBe(true);
+ });
+
+ it('editor contains the value provided', () => {
+ expect(findEditor().props('value')).toBe(mockCiYml);
+ });
+
+ it('editor is configured for the CI config path', () => {
+ expect(findEditor().props('fileName')).toBe(mockCiConfigPath);
+ });
+
+ it('bubbles up events', () => {
+ findEditor().vm.$emit(EDITOR_READY_EVENT);
+
+ expect(editorReadyListener).toHaveBeenCalled();
+ });
+ });
+
+ describe('register CI schema', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ // Since the editor will have already mounted, the event will have fired.
+ // To ensure we properly test this, we clear the mock and re-remit the event.
+ mockRegisterCiSchema.mockClear();
+ mockUse.mockClear();
+
+ findEditor().vm.$emit(EDITOR_READY_EVENT);
+ });
+
+ it('configures editor with syntax highlight', async () => {
+ expect(mockUse).toHaveBeenCalledTimes(1);
+ expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1);
+ expect(mockRegisterCiSchema).toHaveBeenCalledWith({
+ projectNamespace: mockProjectNamespace,
+ projectPath: mockProjectPath,
+ ref: mockCommitSha,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js
new file mode 100644
index 00000000000..df15a6c8e7f
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js
@@ -0,0 +1,34 @@
+import { shallowMount } from '@vue/test-utils';
+import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue';
+import ValidationSegment from '~/pipeline_editor/components/header/validation_segment.vue';
+
+import { mockLintResponse } from '../../mock_data';
+
+describe('Pipeline editor header', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(PipelineEditorHeader, {
+ props: {
+ ciConfigData: mockLintResponse,
+ isCiConfigDataLoading: false,
+ },
+ });
+ };
+
+ const findValidationSegment = () => wrapper.findComponent(ValidationSegment);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+ it('renders the validation segment', () => {
+ expect(findValidationSegment().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/info/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
index 8a991d82018..cf1d89e1d7c 100644
--- a/spec/frontend/pipeline_editor/components/info/validation_segment_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
@@ -1,9 +1,11 @@
-import { escape } from 'lodash';
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { escape } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { sprintf } from '~/locale';
-import ValidationSegment, { i18n } from '~/pipeline_editor/components/info/validation_segment.vue';
+import ValidationSegment, {
+ i18n,
+} from '~/pipeline_editor/components/header/validation_segment.vue';
import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
import { mockYmlHelpPagePath, mergeUnwrappedCiConfig } from '../../mock_data';
@@ -29,6 +31,11 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => {
const findLearnMoreLink = () => wrapper.findByTestId('learnMoreLink');
const findValidationMsg = () => wrapper.findByTestId('validationMsg');
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
it('shows the loading state', () => {
createComponent({ loading: true });
diff --git a/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js
index 5e9471376bd..6775433deb9 100644
--- a/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js
+++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlTable, GlLink } from '@gitlab/ui';
-import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
+import { shallowMount, mount } from '@vue/test-utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
import { mockJobs, mockErrors, mockWarnings } from '../../mock_data';
describe('CI Lint Results', () => {
diff --git a/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js
index 5ccf4bbdab4..fdddca3d62b 100644
--- a/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js
+++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlAlert, GlLink } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
import { mergeUnwrappedCiConfig, mockLintHelpPagePath } from '../../mock_data';
diff --git a/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js
index b441d26c146..4b576508ee9 100644
--- a/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js
+++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlAlert, GlSprintf } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import CiLintWarnings from '~/pipeline_editor/components/lint/ci_lint_warnings.vue';
diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
new file mode 100644
index 00000000000..24af17e9ce6
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -0,0 +1,183 @@
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
+import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
+import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
+import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
+
+import { mockLintResponse, mockCiYml } from '../mock_data';
+
+describe('Pipeline editor tabs component', () => {
+ let wrapper;
+ const MockTextEditor = {
+ template: '<div />',
+ };
+ const mockProvide = {
+ glFeatures: {
+ ciConfigVisualizationTab: true,
+ ciConfigMergedTab: true,
+ },
+ };
+
+ const createComponent = ({ props = {}, provide = {}, mountFn = shallowMount } = {}) => {
+ wrapper = mountFn(PipelineEditorTabs, {
+ propsData: {
+ ciConfigData: mockLintResponse,
+ ciFileContent: mockCiYml,
+ isCiConfigDataLoading: false,
+ ...props,
+ },
+ provide: { ...mockProvide, ...provide },
+ stubs: {
+ TextEditor: MockTextEditor,
+ },
+ });
+ };
+
+ const findEditorTab = () => wrapper.find('[data-testid="editor-tab"]');
+ const findLintTab = () => wrapper.find('[data-testid="lint-tab"]');
+ const findMergedTab = () => wrapper.find('[data-testid="merged-tab"]');
+ const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findCiLint = () => wrapper.findComponent(CiLint);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findPipelineGraph = () => wrapper.findComponent(PipelineGraph);
+ const findTextEditor = () => wrapper.findComponent(MockTextEditor);
+ const findMergedPreview = () => wrapper.findComponent(CiConfigMergedPreview);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('editor tab', () => {
+ it('displays editor only after the tab is mounted', async () => {
+ createComponent({ mountFn: mount });
+
+ expect(findTextEditor().exists()).toBe(false);
+
+ await nextTick();
+
+ expect(findTextEditor().exists()).toBe(true);
+ expect(findEditorTab().exists()).toBe(true);
+ });
+ });
+
+ describe('visualization tab', () => {
+ describe('with feature flag on', () => {
+ describe('while loading', () => {
+ beforeEach(() => {
+ createComponent({ props: { isCiConfigDataLoading: true } });
+ });
+
+ it('displays a loading icon if the lint query is loading', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
+ describe('after loading', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('display the tab and visualization', () => {
+ expect(findVisualizationTab().exists()).toBe(true);
+ expect(findPipelineGraph().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('with feature flag off', () => {
+ beforeEach(() => {
+ createComponent({
+ provide: {
+ glFeatures: { ciConfigVisualizationTab: false },
+ },
+ });
+ });
+
+ it('does not display the tab or component', () => {
+ expect(findVisualizationTab().exists()).toBe(false);
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('lint tab', () => {
+ describe('while loading', () => {
+ beforeEach(() => {
+ createComponent({ props: { isCiConfigDataLoading: true } });
+ });
+
+ it('displays a loading icon if the lint query is loading', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not display the lint component', () => {
+ expect(findCiLint().exists()).toBe(false);
+ });
+ });
+ describe('after loading', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('display the tab and the lint component', () => {
+ expect(findLintTab().exists()).toBe(true);
+ expect(findCiLint().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('merged tab', () => {
+ describe('with feature flag on', () => {
+ describe('while loading', () => {
+ beforeEach(() => {
+ createComponent({ props: { isCiConfigDataLoading: true } });
+ });
+
+ it('displays a loading icon if the lint query is loading', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('when `mergedYaml` is undefined', () => {
+ beforeEach(() => {
+ createComponent({ props: { ciConfigData: {} } });
+ });
+
+ it('show an error message', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts.loadMergedYaml);
+ });
+
+ it('does not render the `meged_preview` component', () => {
+ expect(findMergedPreview().exists()).toBe(false);
+ });
+ });
+
+ describe('after loading', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('display the tab and the merged preview component', () => {
+ expect(findMergedTab().exists()).toBe(true);
+ expect(findMergedPreview().exists()).toBe(true);
+ });
+ });
+ });
+ describe('with feature flag off', () => {
+ beforeEach(() => {
+ createComponent({ provide: { glFeatures: { ciConfigMergedTab: false } } });
+ });
+
+ it('does not display the merged tab', () => {
+ expect(findMergedTab().exists()).toBe(false);
+ expect(findMergedPreview().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/text_editor_spec.js b/spec/frontend/pipeline_editor/components/text_editor_spec.js
deleted file mode 100644
index 9221d64c44b..00000000000
--- a/spec/frontend/pipeline_editor/components/text_editor_spec.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import {
- mockCiConfigPath,
- mockCiYml,
- mockCommitSha,
- mockProjectPath,
- mockProjectNamespace,
-} from '../mock_data';
-
-import TextEditor from '~/pipeline_editor/components/text_editor.vue';
-
-describe('~/pipeline_editor/components/text_editor.vue', () => {
- let wrapper;
-
- let editorReadyListener;
- let mockUse;
- let mockRegisterCiSchema;
-
- const MockEditorLite = {
- template: '<div/>',
- props: ['value', 'fileName'],
- mounted() {
- this.$emit('editor-ready');
- },
- methods: {
- getEditor: () => ({
- use: mockUse,
- registerCiSchema: mockRegisterCiSchema,
- }),
- },
- };
-
- const createComponent = (opts = {}, mountFn = shallowMount) => {
- wrapper = mountFn(TextEditor, {
- provide: {
- projectPath: mockProjectPath,
- projectNamespace: mockProjectNamespace,
- },
- propsData: {
- ciConfigPath: mockCiConfigPath,
- commitSha: mockCommitSha,
- },
- attrs: {
- value: mockCiYml,
- },
- listeners: {
- 'editor-ready': editorReadyListener,
- },
- stubs: {
- EditorLite: MockEditorLite,
- },
- ...opts,
- });
- };
-
- const findEditor = () => wrapper.find(MockEditorLite);
-
- beforeEach(() => {
- editorReadyListener = jest.fn();
- mockUse = jest.fn();
- mockRegisterCiSchema = jest.fn();
-
- createComponent();
- });
-
- it('contains an editor', () => {
- expect(findEditor().exists()).toBe(true);
- });
-
- it('editor contains the value provided', () => {
- expect(findEditor().props('value')).toBe(mockCiYml);
- });
-
- it('editor is configured for the CI config path', () => {
- expect(findEditor().props('fileName')).toBe(mockCiConfigPath);
- });
-
- it('editor is configured with syntax highligting', async () => {
- expect(mockUse).toHaveBeenCalledTimes(1);
- expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1);
- expect(mockRegisterCiSchema).toHaveBeenCalledWith({
- projectNamespace: mockProjectNamespace,
- projectPath: mockProjectPath,
- ref: mockCommitSha,
- });
- });
-
- it('bubbles up events', () => {
- findEditor().vm.$emit('editor-ready');
-
- expect(editorReadyListener).toHaveBeenCalled();
- });
-});
diff --git a/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js b/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js
new file mode 100644
index 00000000000..44fda2812d8
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js
@@ -0,0 +1,42 @@
+import { shallowMount } from '@vue/test-utils';
+import ConfirmDialog from '~/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue';
+
+describe('pipeline_editor/components/ui/confirm_unsaved_changes_dialog', () => {
+ let beforeUnloadEvent;
+ let setDialogContent;
+ let wrapper;
+
+ const createComponent = (propsData = {}) => {
+ wrapper = shallowMount(ConfirmDialog, {
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ beforeUnloadEvent = new Event('beforeunload');
+ jest.spyOn(beforeUnloadEvent, 'preventDefault');
+ setDialogContent = jest.spyOn(beforeUnloadEvent, 'returnValue', 'set');
+ });
+
+ afterEach(() => {
+ beforeUnloadEvent.preventDefault.mockRestore();
+ setDialogContent.mockRestore();
+ wrapper.destroy();
+ });
+
+ it('shows confirmation dialog when there are unsaved changes', () => {
+ createComponent({ hasUnsavedChanges: true });
+ window.dispatchEvent(beforeUnloadEvent);
+
+ expect(beforeUnloadEvent.preventDefault).toHaveBeenCalled();
+ expect(setDialogContent).toHaveBeenCalledWith('');
+ });
+
+ it('does not show confirmation dialog when there are no unsaved changes', () => {
+ createComponent({ hasUnsavedChanges: false });
+ window.dispatchEvent(beforeUnloadEvent);
+
+ expect(beforeUnloadEvent.preventDefault).not.toHaveBeenCalled();
+ expect(setDialogContent).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
index d3d9bf08209..291468c5229 100644
--- a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
+++ b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
@@ -1,6 +1,6 @@
-import { nextTick } from 'vue';
-import { mount } from '@vue/test-utils';
import { GlTabs } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue';
diff --git a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
index 3e008527415..d39c0d80296 100644
--- a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
+++ b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
@@ -1,5 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import Api from '~/api';
+import axios from '~/lib/utils/axios_utils';
+import httpStatus from '~/lib/utils/http_status';
+import { resolvers } from '~/pipeline_editor/graphql/resolvers';
import {
mockCiConfigPath,
mockCiYml,
@@ -7,9 +10,6 @@ import {
mockLintResponse,
mockProjectFullPath,
} from '../mock_data';
-import httpStatus from '~/lib/utils/http_status';
-import axios from '~/lib/utils/axios_utils';
-import { resolvers } from '~/pipeline_editor/graphql/resolvers';
jest.mock('~/api', () => {
return {
diff --git a/spec/frontend/pipeline_editor/mock_data.js b/spec/frontend/pipeline_editor/mock_data.js
index 3eacc467c51..8e248c11b87 100644
--- a/spec/frontend/pipeline_editor/mock_data.js
+++ b/spec/frontend/pipeline_editor/mock_data.js
@@ -54,6 +54,7 @@ export const mockCiConfigQueryResponse = {
data: {
ciConfig: {
errors: [],
+ mergedYaml: mockCiYml,
status: CI_CONFIG_STATUS_VALID,
stages: {
__typename: 'CiConfigStageConnection',
@@ -139,6 +140,8 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
export const mockLintResponse = {
valid: true,
+ mergedYaml: mockCiYml,
+ status: CI_CONFIG_STATUS_VALID,
errors: [],
warnings: [],
jobs: [
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index d6b90900600..46d0452f437 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -1,119 +1,70 @@
-import { nextTick } from 'vue';
-import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
-import { GlAlert, GlButton, GlFormInput, GlFormTextarea, GlLoadingIcon, GlTabs } from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
+import { GlAlert, GlButton, GlLoadingIcon, GlTabs } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-
+import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status';
-import { objectToQuery, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility';
+import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
+import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
+
+import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants';
+import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
+import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
+import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import {
mockCiConfigPath,
mockCiConfigQueryResponse,
mockCiYml,
- mockCommitSha,
- mockCommitNextSha,
- mockCommitMessage,
mockDefaultBranch,
- mockProjectPath,
mockProjectFullPath,
- mockProjectNamespace,
- mockNewMergeRequestPath,
} from './mock_data';
-import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
-import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
-import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue';
-import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
-import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
-import TextEditor from '~/pipeline_editor/components/text_editor.vue';
-
const localVue = createLocalVue();
localVue.use(VueApollo);
-jest.mock('~/lib/utils/url_utility', () => ({
- redirectTo: jest.fn(),
- refreshCurrentPage: jest.fn(),
- objectToQuery: jest.requireActual('~/lib/utils/url_utility').objectToQuery,
- mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
-}));
-
const MockEditorLite = {
template: '<div/>',
};
const mockProvide = {
+ ciConfigPath: mockCiConfigPath,
+ defaultBranch: mockDefaultBranch,
projectFullPath: mockProjectFullPath,
- projectPath: mockProjectPath,
- projectNamespace: mockProjectNamespace,
- glFeatures: {
- ciConfigVisualizationTab: true,
- },
};
-describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
+describe('Pipeline editor app component', () => {
let wrapper;
let mockApollo;
let mockBlobContentData;
let mockCiConfigData;
- let mockMutate;
-
- const createComponent = ({
- props = {},
- blobLoading = false,
- lintLoading = false,
- options = {},
- mountFn = shallowMount,
- provide = mockProvide,
- } = {}) => {
- mockMutate = jest.fn().mockResolvedValue({
- data: {
- commitCreate: {
- errors: [],
- commit: {
- sha: mockCommitNextSha,
- },
- },
- },
- });
- wrapper = mountFn(PipelineEditorApp, {
- propsData: {
- ciConfigPath: mockCiConfigPath,
- commitSha: mockCommitSha,
- defaultBranch: mockDefaultBranch,
- newMergeRequestPath: mockNewMergeRequestPath,
- ...props,
- },
- provide,
+ const createComponent = ({ blobLoading = false, options = {} } = {}) => {
+ wrapper = shallowMount(PipelineEditorApp, {
+ provide: mockProvide,
stubs: {
GlTabs,
GlButton,
CommitForm,
EditorLite: MockEditorLite,
- TextEditor,
},
mocks: {
$apollo: {
queries: {
- content: {
+ initialCiFileContent: {
loading: blobLoading,
},
ciConfigData: {
- loading: lintLoading,
+ loading: false,
},
},
- mutate: mockMutate,
},
},
- // attachTo is required for input/submit events
- attachTo: mountFn === mount ? document.body : null,
...options,
});
};
- const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
+ const createComponentWithApollo = ({ props = {} } = {}) => {
const handlers = [[getCiConfigData, mockCiConfigData]];
const resolvers = {
Query: {
@@ -134,18 +85,13 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
apolloProvider: mockApollo,
};
- createComponent({ props, options }, mountFn);
+ createComponent({ props, options });
};
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findAlert = () => wrapper.find(GlAlert);
- const findTabAt = (i) => wrapper.findAll(EditorTab).at(i);
- const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
- const findTextEditor = () => wrapper.find(TextEditor);
- const findEditorLite = () => wrapper.find(MockEditorLite);
- const findCommitForm = () => wrapper.find(CommitForm);
- const findPipelineGraph = () => wrapper.find(PipelineGraph);
- const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findEditorHome = () => wrapper.findComponent(PipelineEditorHome);
+ const findTextEditor = () => wrapper.findComponent(TextEditor);
beforeEach(() => {
mockBlobContentData = jest.fn();
@@ -155,9 +101,6 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
afterEach(() => {
mockBlobContentData.mockReset();
mockCiConfigData.mockReset();
- refreshCurrentPage.mockReset();
- redirectTo.mockReset();
- mockMutate.mockReset();
wrapper.destroy();
wrapper = null;
@@ -170,245 +113,6 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
expect(findTextEditor().exists()).toBe(false);
});
- describe('tabs', () => {
- describe('editor tab', () => {
- it('displays editor only after the tab is mounted', async () => {
- createComponent({ mountFn: mount });
-
- expect(findTabAt(0).find(TextEditor).exists()).toBe(false);
-
- await nextTick();
-
- expect(findTabAt(0).find(TextEditor).exists()).toBe(true);
- });
- });
-
- describe('visualization tab', () => {
- describe('with feature flag on', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('display the tab', () => {
- expect(findVisualizationTab().exists()).toBe(true);
- });
-
- it('displays a loading icon if the lint query is loading', () => {
- createComponent({ lintLoading: true });
-
- expect(findLoadingIcon().exists()).toBe(true);
- expect(findPipelineGraph().exists()).toBe(false);
- });
- });
-
- describe('with feature flag off', () => {
- beforeEach(() => {
- createComponent({
- provide: {
- ...mockProvide,
- glFeatures: { ciConfigVisualizationTab: false },
- },
- });
- });
-
- it('does not display the tab', () => {
- expect(findVisualizationTab().exists()).toBe(false);
- });
- });
- });
- });
-
- describe('when data is set', () => {
- beforeEach(async () => {
- createComponent({ mountFn: mount });
-
- wrapper.setData({
- content: mockCiYml,
- contentModel: mockCiYml,
- });
-
- await waitForPromises();
- });
-
- it('displays content after the query loads', () => {
- expect(findLoadingIcon().exists()).toBe(false);
-
- expect(findEditorLite().attributes('value')).toBe(mockCiYml);
- expect(findEditorLite().attributes('file-name')).toBe(mockCiConfigPath);
- });
-
- it('configures text editor', () => {
- expect(findTextEditor().props('commitSha')).toBe(mockCommitSha);
- });
-
- describe('commit form', () => {
- const mockVariables = {
- content: mockCiYml,
- filePath: mockCiConfigPath,
- lastCommitId: mockCommitSha,
- message: mockCommitMessage,
- projectPath: mockProjectFullPath,
- startBranch: mockDefaultBranch,
- };
-
- const findInForm = (selector) => findCommitForm().find(selector);
-
- const submitCommit = async ({
- message = mockCommitMessage,
- branch = mockDefaultBranch,
- openMergeRequest = false,
- } = {}) => {
- await findInForm(GlFormTextarea).setValue(message);
- await findInForm(GlFormInput).setValue(branch);
- if (openMergeRequest) {
- await findInForm('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest);
- }
- await findInForm('[type="submit"]').trigger('click');
- };
-
- const cancelCommitForm = async () => {
- const findCancelBtn = () => wrapper.find('[type="reset"]');
- await findCancelBtn().trigger('click');
- };
-
- describe('when the user commits changes to the current branch', () => {
- beforeEach(async () => {
- await submitCommit();
- });
-
- it('calls the mutation with the default branch', () => {
- expect(mockMutate).toHaveBeenCalledWith({
- mutation: expect.any(Object),
- variables: {
- ...mockVariables,
- branch: mockDefaultBranch,
- },
- });
- });
-
- it('displays an alert to indicate success', () => {
- expect(findAlert().text()).toMatchInterpolatedText(
- 'Your changes have been successfully committed.',
- );
- });
-
- it('shows no saving state', () => {
- expect(findCommitBtnLoadingIcon().exists()).toBe(false);
- });
-
- it('a second commit submits the latest sha, keeping the form updated', async () => {
- await submitCommit();
-
- expect(mockMutate).toHaveBeenCalledTimes(2);
- expect(mockMutate).toHaveBeenLastCalledWith({
- mutation: expect.any(Object),
- variables: {
- ...mockVariables,
- lastCommitId: mockCommitNextSha,
- branch: mockDefaultBranch,
- },
- });
- });
- });
-
- describe('when the user commits changes to a new branch', () => {
- const newBranch = 'new-branch';
-
- beforeEach(async () => {
- await submitCommit({
- branch: newBranch,
- });
- });
-
- it('calls the mutation with the new branch', () => {
- expect(mockMutate).toHaveBeenCalledWith({
- mutation: expect.any(Object),
- variables: {
- ...mockVariables,
- branch: newBranch,
- },
- });
- });
- });
-
- describe('when the user commits changes to open a new merge request', () => {
- const newBranch = 'new-branch';
-
- beforeEach(async () => {
- await submitCommit({
- branch: newBranch,
- openMergeRequest: true,
- });
- });
-
- it('redirects to the merge request page with source and target branches', () => {
- const branchesQuery = objectToQuery({
- 'merge_request[source_branch]': newBranch,
- 'merge_request[target_branch]': mockDefaultBranch,
- });
-
- expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`);
- });
- });
-
- describe('when the commit is ocurring', () => {
- it('shows a saving state', async () => {
- await mockMutate.mockImplementationOnce(() => {
- expect(findCommitBtnLoadingIcon().exists()).toBe(true);
- return Promise.resolve();
- });
-
- await submitCommit({
- message: mockCommitMessage,
- branch: mockDefaultBranch,
- openMergeRequest: false,
- });
- });
- });
-
- describe('when the commit fails', () => {
- it('shows an error message', async () => {
- mockMutate.mockRejectedValueOnce(new Error('commit failed'));
-
- await submitCommit();
-
- await waitForPromises();
-
- expect(findAlert().text()).toMatchInterpolatedText(
- 'The GitLab CI configuration could not be updated. commit failed',
- );
- });
-
- it('shows an unkown error', async () => {
- mockMutate.mockRejectedValueOnce();
-
- await submitCommit();
-
- await waitForPromises();
-
- expect(findAlert().text()).toMatchInterpolatedText(
- 'The GitLab CI configuration could not be updated.',
- );
- });
- });
-
- describe('when the commit form is cancelled', () => {
- const otherContent = 'other content';
-
- beforeEach(async () => {
- findTextEditor().vm.$emit('input', otherContent);
- await nextTick();
- });
-
- it('content is restored after cancel is called', async () => {
- await cancelCommitForm();
-
- expect(findEditorLite().attributes('value')).toBe(mockCiYml);
- });
- });
- });
- });
-
describe('when queries are called', () => {
beforeEach(() => {
mockBlobContentData.mockResolvedValue(mockCiYml);
@@ -422,14 +126,12 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
});
- it('shows editor and commit form', () => {
- expect(findEditorLite().exists()).toBe(true);
- expect(findTextEditor().exists()).toBe(true);
+ it('shows pipeline editor home component', () => {
+ expect(findEditorHome().exists()).toBe(true);
});
- it('no error is shown when data is set', async () => {
+ it('no error is shown when data is set', () => {
expect(findAlert().exists()).toBe(false);
- expect(findEditorLite().attributes('value')).toBe(mockCiYml);
});
it('ci config query is called with correct variables', async () => {
@@ -445,10 +147,10 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
});
describe('when no file exists', () => {
- const expectedAlertMsg =
+ const noFileAlertMsg =
'There is no .gitlab-ci.yml file in this repository, please add one and visit the Pipeline Editor again.';
- it('shows a 404 error message and does not show editor or commit form', async () => {
+ it('shows a 404 error message and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.NOT_FOUND,
@@ -458,12 +160,11 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
- expect(findAlert().text()).toBe(expectedAlertMsg);
- expect(findEditorLite().exists()).toBe(false);
- expect(findTextEditor().exists()).toBe(false);
+ expect(findAlert().text()).toBe(noFileAlertMsg);
+ expect(findEditorHome().exists()).toBe(false);
});
- it('shows a 400 error message and does not show editor or commit form', async () => {
+ it('shows a 400 error message and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.BAD_REQUEST,
@@ -473,9 +174,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
- expect(findAlert().text()).toBe(expectedAlertMsg);
- expect(findEditorLite().exists()).toBe(false);
- expect(findTextEditor().exists()).toBe(false);
+ expect(findAlert().text()).toBe(noFileAlertMsg);
+ expect(findEditorHome().exists()).toBe(false);
});
it('shows a unkown error message', async () => {
@@ -483,9 +183,60 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
createComponentWithApollo();
await waitForPromises();
- expect(findAlert().text()).toBe('The CI configuration was not loaded, please try again.');
- expect(findEditorLite().exists()).toBe(true);
- expect(findTextEditor().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[LOAD_FAILURE_UNKNOWN]);
+ expect(findEditorHome().exists()).toBe(true);
+ });
+ });
+
+ describe('when the user commits', () => {
+ const updateFailureMessage = 'The GitLab CI configuration could not be updated.';
+
+ describe('and the commit mutation succeeds', () => {
+ beforeEach(() => {
+ createComponent();
+
+ findEditorHome().vm.$emit('commit', { type: COMMIT_SUCCESS });
+ });
+
+ it('shows a confirmation message', () => {
+ expect(findAlert().text()).toBe(wrapper.vm.$options.successTexts[COMMIT_SUCCESS]);
+ });
+ });
+ describe('and the commit mutation fails', () => {
+ const commitFailedReasons = ['Commit failed'];
+
+ beforeEach(() => {
+ createComponent();
+
+ findEditorHome().vm.$emit('showError', {
+ type: COMMIT_FAILURE,
+ reasons: commitFailedReasons,
+ });
+ });
+
+ it('shows an error message', () => {
+ expect(findAlert().text()).toMatchInterpolatedText(
+ `${updateFailureMessage} ${commitFailedReasons[0]}`,
+ );
+ });
+ });
+ describe('when an unknown error occurs', () => {
+ const unknownReasons = ['Commit failed'];
+
+ beforeEach(() => {
+ createComponent();
+
+ findEditorHome().vm.$emit('showError', {
+ type: COMMIT_FAILURE,
+ reasons: unknownReasons,
+ });
+ });
+
+ it('shows an error message', () => {
+ expect(findAlert().text()).toMatchInterpolatedText(
+ `${updateFailureMessage} ${unknownReasons[0]}`,
+ );
+ });
});
});
});
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
new file mode 100644
index 00000000000..9864f3c13f9
--- /dev/null
+++ b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+
+import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
+import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue';
+import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
+import { MERGED_TAB, VISUALIZE_TAB } from '~/pipeline_editor/constants';
+import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
+
+import { mockLintResponse, mockCiYml } from './mock_data';
+
+describe('Pipeline editor home wrapper', () => {
+ let wrapper;
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMount(PipelineEditorHome, {
+ propsData: {
+ ciConfigData: mockLintResponse,
+ ciFileContent: mockCiYml,
+ isCiConfigDataLoading: false,
+ ...props,
+ },
+ });
+ };
+
+ const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader);
+ const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs);
+ const findCommitSection = () => wrapper.findComponent(CommitSection);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('renders', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows the pipeline editor header', () => {
+ expect(findPipelineEditorHeader().exists()).toBe(true);
+ });
+
+ it('shows the pipeline editor tabs', () => {
+ expect(findPipelineEditorTabs().exists()).toBe(true);
+ });
+
+ it('shows the commit section by default', () => {
+ expect(findCommitSection().exists()).toBe(true);
+ });
+ });
+
+ describe('commit form toggle', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('hides the commit form when in the merged tab', async () => {
+ expect(findCommitSection().exists()).toBe(true);
+
+ findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
+ await nextTick();
+ expect(findCommitSection().exists()).toBe(false);
+ });
+
+ it('shows the form again when leaving the merged tab', async () => {
+ expect(findCommitSection().exists()).toBe(true);
+
+ findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
+ await nextTick();
+ expect(findCommitSection().exists()).toBe(false);
+
+ findPipelineEditorTabs().vm.$emit('set-current-tab', VISUALIZE_TAB);
+ await nextTick();
+ expect(findCommitSection().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
index 421ad9f4939..51bb0ecee9c 100644
--- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
@@ -1,9 +1,10 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlForm, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+import { redirectTo } from '~/lib/utils/url_utility';
import PipelineNewForm from '~/pipeline_new/components/pipeline_new_form.vue';
import {
mockBranches,
@@ -13,7 +14,6 @@ import {
mockProjectId,
mockError,
} from '../mock_data';
-import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
@@ -34,6 +34,7 @@ describe('Pipeline New Form', () => {
const findForm = () => wrapper.find(GlForm);
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+ const findSubmitButton = () => wrapper.find('[data-testid="run_pipeline_button"]');
const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]');
const findRemoveIcons = () => wrapper.findAll('[data-testid="remove-ci-variable-row"]');
const findKeyInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-key"]');
@@ -155,6 +156,18 @@ describe('Pipeline New Form', () => {
await waitForPromises();
});
+
+ it('disables the submit button immediately after submitting', async () => {
+ createComponent();
+
+ expect(findSubmitButton().props('disabled')).toBe(false);
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+ await waitForPromises();
+
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ });
+
it('creates pipeline with full ref and variables', async () => {
createComponent();
@@ -167,6 +180,7 @@ describe('Pipeline New Form', () => {
expect(getExpectedPostParams().ref).toEqual(wrapper.vm.$data.refValue.fullName);
expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${postResponse.id}`);
});
+
it('creates a pipeline with short ref and variables', async () => {
// query params are used
createComponent('', mockParams);
@@ -225,42 +239,47 @@ describe('Pipeline New Form', () => {
});
});
- describe('when feature flag new_pipeline_form_prefilled_vars is enabled', () => {
- let origGon;
-
+ describe('when yml defines a variable', () => {
const mockYmlKey = 'yml_var';
const mockYmlValue = 'yml_var_val';
+ const mockYmlMultiLineValue = `A value
+ with multiple
+ lines`;
const mockYmlDesc = 'A var from yml.';
- beforeAll(() => {
- origGon = window.gon;
- window.gon = { features: { newPipelineFormPrefilledVars: true } };
- });
+ it('loading icon is shown when content is requested and hidden when received', async () => {
+ createComponent('', mockParams, mount);
- afterAll(() => {
- window.gon = origGon;
- });
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
+ [mockYmlKey]: {
+ value: mockYmlValue,
+ description: mockYmlDesc,
+ },
+ });
- describe('loading state', () => {
- it('loading icon is shown when content is requested and hidden when received', async () => {
- createComponent('', mockParams, mount);
+ expect(findLoadingIcon().exists()).toBe(true);
- mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
- [mockYmlKey]: {
- value: mockYmlValue,
- description: mockYmlDesc,
- },
- });
+ await waitForPromises();
- expect(findLoadingIcon().exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
- await waitForPromises();
+ it('multi-line strings are added to the value field without removing line breaks', async () => {
+ createComponent('', mockParams, mount);
- expect(findLoadingIcon().exists()).toBe(false);
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
+ [mockYmlKey]: {
+ value: mockYmlMultiLineValue,
+ description: mockYmlDesc,
+ },
});
+
+ await waitForPromises();
+
+ expect(findValueInputs().at(0).element.value).toBe(mockYmlMultiLineValue);
});
- describe('when yml defines a variable with description', () => {
+ describe('with description', () => {
beforeEach(async () => {
createComponent('', mockParams, mount);
@@ -302,7 +321,7 @@ describe('Pipeline New Form', () => {
});
});
- describe('when yml defines a variable without description', () => {
+ describe('without description', () => {
beforeEach(async () => {
createComponent('', mockParams, mount);
@@ -325,31 +344,55 @@ describe('Pipeline New Form', () => {
describe('Form errors and warnings', () => {
beforeEach(() => {
createComponent();
+ });
- mock.onPost(pipelinesPath).reply(httpStatusCodes.BAD_REQUEST, mockError);
+ describe('when the error response can be handled', () => {
+ beforeEach(async () => {
+ mock.onPost(pipelinesPath).reply(httpStatusCodes.BAD_REQUEST, mockError);
- findForm().vm.$emit('submit', dummySubmitEvent);
+ findForm().vm.$emit('submit', dummySubmitEvent);
- return waitForPromises();
- });
+ await waitForPromises();
+ });
- it('shows both error and warning', () => {
- expect(findErrorAlert().exists()).toBe(true);
- expect(findWarningAlert().exists()).toBe(true);
- });
+ it('shows both error and warning', () => {
+ expect(findErrorAlert().exists()).toBe(true);
+ expect(findWarningAlert().exists()).toBe(true);
+ });
- it('shows the correct error', () => {
- expect(findErrorAlert().text()).toBe(mockError.errors[0]);
- });
+ it('shows the correct error', () => {
+ expect(findErrorAlert().text()).toBe(mockError.errors[0]);
+ });
- it('shows the correct warning title', () => {
- const { length } = mockError.warnings;
+ it('shows the correct warning title', () => {
+ const { length } = mockError.warnings;
+
+ expect(findWarningAlertSummary().attributes('message')).toBe(`${length} warnings found:`);
+ });
- expect(findWarningAlertSummary().attributes('message')).toBe(`${length} warnings found:`);
+ it('shows the correct amount of warnings', () => {
+ expect(findWarnings()).toHaveLength(mockError.warnings.length);
+ });
+
+ it('re-enables the submit button', () => {
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ });
});
- it('shows the correct amount of warnings', () => {
- expect(findWarnings()).toHaveLength(mockError.warnings.length);
+ describe('when the error response cannot be handled', () => {
+ beforeEach(async () => {
+ mock
+ .onPost(pipelinesPath)
+ .reply(httpStatusCodes.INTERNAL_SERVER_ERROR, 'something went wrong');
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+
+ await waitForPromises();
+ });
+
+ it('re-enables the submit button', () => {
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ });
});
});
});
diff --git a/spec/frontend/pipeline_new/utils/format_refs_spec.js b/spec/frontend/pipeline_new/utils/format_refs_spec.js
index 1fda6a8af83..405a747c3ba 100644
--- a/spec/frontend/pipeline_new/utils/format_refs_spec.js
+++ b/spec/frontend/pipeline_new/utils/format_refs_spec.js
@@ -1,5 +1,5 @@
-import formatRefs from '~/pipeline_new/utils/format_refs';
import { BRANCH_REF_TYPE, TAG_REF_TYPE } from '~/pipeline_new/constants';
+import formatRefs from '~/pipeline_new/utils/format_refs';
import { mockBranchRefs, mockTagRefs } from '../mock_data';
describe('Format refs util', () => {
diff --git a/spec/frontend/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js
index c09d9232569..5dcf3d267ed 100644
--- a/spec/frontend/pipelines/blank_state_spec.js
+++ b/spec/frontend/pipelines/blank_state_spec.js
@@ -1,25 +1,20 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import component from '~/pipelines/components/pipelines_list/blank_state.vue';
+import { getByText } from '@testing-library/dom';
+import { mount } from '@vue/test-utils';
+import BlankState from '~/pipelines/components/pipelines_list/blank_state.vue';
describe('Pipelines Blank State', () => {
- let vm;
- let Component;
-
- beforeEach(() => {
- Component = Vue.extend(component);
-
- vm = mountComponent(Component, {
+ const wrapper = mount(BlankState, {
+ propsData: {
svgPath: 'foo',
message: 'Blank State',
- });
+ },
});
it('should render svg', () => {
- expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toEqual('foo');
+ expect(wrapper.find('.svg-content img').attributes('src')).toEqual('foo');
});
it('should render message', () => {
- expect(vm.$el.querySelector('h4').textContent.trim()).toEqual('Blank State');
+ expect(getByText(wrapper.element, /Blank State/i)).toBeTruthy();
});
});
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
index 80807c0b330..1941a7f2777 100644
--- a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
import { singleNote, multiNote } from './mock_data';
diff --git a/spec/frontend/pipelines/components/dag/dag_graph_spec.js b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
index ccfb2ae7cee..4619548d1bb 100644
--- a/spec/frontend/pipelines/components/dag/dag_graph_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import { IS_HIGHLIGHTED, LINK_SELECTOR, NODE_SELECTOR } from '~/pipelines/components/dag/constants';
-import { highlightIn, highlightOut } from '~/pipelines/components/dag/interactions';
+import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import { createSankey } from '~/pipelines/components/dag/drawing_utils';
+import { highlightIn, highlightOut } from '~/pipelines/components/dag/interactions';
import { removeOrphanNodes } from '~/pipelines/components/parsing_utils';
import { parsedData } from './mock_data';
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index f6195e30e44..14030930657 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -1,10 +1,10 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlAlert, GlEmptyState } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+import { ADD_NOTE, REMOVE_NOTE, REPLACE_NOTES } from '~/pipelines/components/dag/constants';
import Dag from '~/pipelines/components/dag/dag.vue';
-import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
+import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
-import { ADD_NOTE, REMOVE_NOTE, REPLACE_NOTES } from '~/pipelines/components/dag/constants';
import { PARSE_FAILURE, UNSUPPORTED_DATA } from '~/pipelines/constants';
import {
mockParsedGraphQLNodes,
diff --git a/spec/frontend/pipelines/components/dag/parsing_utils_spec.js b/spec/frontend/pipelines/components/dag/parsing_utils_spec.js
index 5d3f680a57c..84ff83883b7 100644
--- a/spec/frontend/pipelines/components/dag/parsing_utils_spec.js
+++ b/spec/frontend/pipelines/components/dag/parsing_utils_spec.js
@@ -1,3 +1,4 @@
+import { createSankey } from '~/pipelines/components/dag/drawing_utils';
import {
createNodeDict,
makeLinksFromNodes,
@@ -7,7 +8,6 @@ import {
getMaxNodes,
} from '~/pipelines/components/parsing_utils';
-import { createSankey } from '~/pipelines/components/dag/drawing_utils';
import { mockParsedGraphQLNodes } from './mock_data';
describe('DAG visualization parsing utilities', () => {
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index 00fe9e784b3..e43aa2a02f5 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -1,6 +1,6 @@
+import { GlFilteredSearch } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { GlFilteredSearch } from '@gitlab/ui';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index 7e42a3b5ae9..3ebedc9ac87 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,7 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { withGonExperiment } from 'helpers/experimentation_helper';
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
-import Tracking from '~/tracking';
describe('Pipelines Empty State', () => {
let wrapper;
@@ -40,104 +38,15 @@ describe('Pipelines Empty State', () => {
expect(findGetStartedButton().attributes('href')).toBe('foo');
});
- describe('when in control group', () => {
- it('should render empty state information', () => {
- expect(findInfoText()).toContain(
- 'Continuous Integration can help catch bugs by running your tests automatically',
- 'while Continuous Deployment can help you deliver code to your product environment',
- );
- });
-
- it('should render a button', () => {
- expect(findGetStartedButton().text()).toBe('Get started with Pipelines');
- });
- });
-
- describe('when in experiment group', () => {
- withGonExperiment('pipelinesEmptyState');
-
- beforeEach(() => {
- createWrapper();
- });
-
- it('should render empty state information', () => {
- expect(findInfoText()).toContain(
- 'GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time',
- 'consuming tasks, so you can spend more time creating',
- );
- });
-
- it('should render button text', () => {
- expect(findGetStartedButton().text()).toBe('Get started with CI/CD');
- });
+ it('should render empty state information', () => {
+ expect(findInfoText()).toContain(
+ 'GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time',
+ 'consuming tasks, so you can spend more time creating',
+ );
});
- describe('tracking', () => {
- let origGon;
-
- describe('when data is set', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event').mockImplementation(() => {});
- origGon = window.gon;
-
- window.gon = {
- tracking_data: {
- category: 'Growth::Activation::Experiment::PipelinesEmptyState',
- value: 1,
- property: 'experimental_group',
- label: 'label',
- },
- };
- createWrapper();
- });
-
- afterEach(() => {
- window.gon = origGon;
- });
-
- it('tracks when mounted', () => {
- expect(Tracking.event).toHaveBeenCalledWith(
- 'Growth::Activation::Experiment::PipelinesEmptyState',
- 'viewed',
- {
- value: 1,
- label: 'label',
- property: 'experimental_group',
- },
- );
- });
-
- it('tracks when button is clicked', () => {
- findGetStartedButton().vm.$emit('click');
-
- expect(Tracking.event).toHaveBeenCalledWith(
- 'Growth::Activation::Experiment::PipelinesEmptyState',
- 'documentation_clicked',
- {
- value: 1,
- label: 'label',
- property: 'experimental_group',
- },
- );
- });
- });
-
- describe('when no data is defined', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event').mockImplementation(() => {});
-
- createWrapper();
- });
-
- it('does not track on view', () => {
- expect(Tracking.event).not.toHaveBeenCalled();
- });
-
- it('does not track when button is clicked', () => {
- findGetStartedButton().vm.$emit('click');
- expect(Tracking.event).not.toHaveBeenCalled();
- });
- });
+ it('should render button text', () => {
+ expect(findGetStartedButton().text()).toBe('Get started with CI/CD');
});
});
});
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js
index 95d96e127c6..6a7018fa1e5 100644
--- a/spec/frontend/pipelines/graph/action_component_spec.js
+++ b/spec/frontend/pipelines/graph/action_component_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js
index 840b1f8baf5..a955572a481 100644
--- a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js
@@ -1,14 +1,14 @@
-import { nextTick } from 'vue';
-import { mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { setHTMLFixture } from 'helpers/fixtures';
-import PipelineStore from '~/pipelines/stores/pipeline_store';
import GraphComponentLegacy from '~/pipelines/components/graph/graph_component_legacy.vue';
-import StageColumnComponentLegacy from '~/pipelines/components/graph/stage_column_component_legacy.vue';
import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue';
-import graphJSON from './mock_data_legacy';
-import linkedPipelineJSON from './linked_pipelines_mock_data';
+import StageColumnComponentLegacy from '~/pipelines/components/graph/stage_column_component_legacy.vue';
import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
+import PipelineStore from '~/pipelines/stores/pipeline_store';
+import linkedPipelineJSON from './linked_pipelines_mock_data';
+import graphJSON from './mock_data_legacy';
describe('graph component', () => {
let store;
diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js
index cfc3b7af282..3e8d4ba314c 100644
--- a/spec/frontend/pipelines/graph/graph_component_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_spec.js
@@ -1,10 +1,10 @@
import { mount, shallowMount } from '@vue/test-utils';
+import { GRAPHQL } from '~/pipelines/components/graph/constants';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
-import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import JobItem from '~/pipelines/components/graph/job_item.vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
+import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
-import { GRAPHQL } from '~/pipelines/components/graph/constants';
import {
generateResponse,
mockPipelineResponse,
@@ -22,6 +22,13 @@ describe('graph component', () => {
pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'),
};
+ const defaultData = {
+ measurements: {
+ width: 800,
+ height: 800,
+ },
+ };
+
const createComponent = ({
data = {},
mountFn = shallowMount,
@@ -34,7 +41,10 @@ describe('graph component', () => {
...props,
},
data() {
- return { ...data };
+ return {
+ ...defaultData,
+ ...data,
+ };
},
provide: {
dataMethod: GRAPHQL,
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 54593c527cb..202365ecd35 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -1,11 +1,11 @@
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { shallowMount } from '@vue/test-utils';
-import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
-import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
+import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
import { mockPipelineResponse } from './mock_data';
const defaultProvide = {
diff --git a/spec/frontend/pipelines/graph/job_name_component_spec.js b/spec/frontend/pipelines/graph/job_name_component_spec.js
index f0aa646b8d7..658b5be87d4 100644
--- a/spec/frontend/pipelines/graph/job_name_component_spec.js
+++ b/spec/frontend/pipelines/graph/job_name_component_spec.js
@@ -1,7 +1,6 @@
import { mount } from '@vue/test-utils';
-import ciIcon from '~/vue_shared/components/ci_icon.vue';
-
import jobNameComponent from '~/pipelines/components/graph/job_name_component.vue';
+import ciIcon from '~/vue_shared/components/ci_icon.vue';
describe('job name component', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index fb005d628a9..96f2cd1e371 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -1,9 +1,10 @@
-import { mount } from '@vue/test-utils';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
+import { UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import mockData from './linked_pipelines_mock_data';
-import { UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants';
const mockPipeline = mockData.triggered[0];
const validTriggeredPipelineId = mockPipeline.project.id;
@@ -212,11 +213,11 @@ describe('Linked pipeline', () => {
expect(wrapper.emitted().pipelineClicked).toBeTruthy();
});
- it('should emit `bv::hide::tooltip` to close the tooltip', () => {
+ it(`should emit ${BV_HIDE_TOOLTIP} to close the tooltip`, () => {
jest.spyOn(wrapper.vm.$root, '$emit');
findButton().trigger('click');
- expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual(['bv::hide::tooltip']);
+ expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual([BV_HIDE_TOOLTIP]);
});
it('should emit downstreamHovered with job name on mouseover', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js
index b6c700c65d2..200e3f48401 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue';
-import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
import { UPSTREAM } from '~/pipelines/components/graph/constants';
+import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
+import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue';
import mockData from './linked_pipelines_mock_data';
describe('Linked Pipelines Column', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 6db152f2607..8f01accccc1 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -1,11 +1,11 @@
-import VueApollo from 'vue-apollo';
import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
+import { DOWNSTREAM, GRAPHQL, UPSTREAM } from '~/pipelines/components/graph/constants';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
-import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
-import { DOWNSTREAM, GRAPHQL } from '~/pipelines/components/graph/constants';
+import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import { LOAD_FAILURE } from '~/pipelines/constants';
import {
mockPipelineResponse,
@@ -17,7 +17,7 @@ const processedPipeline = pipelineWithUpstreamDownstream(mockPipelineResponse);
describe('Linked Pipelines Column', () => {
const defaultProps = {
- columnTitle: 'Upstream',
+ columnTitle: 'Downstream',
linkedPipelines: processedPipeline.downstream,
type: DOWNSTREAM,
};
@@ -45,14 +45,15 @@ describe('Linked Pipelines Column', () => {
});
};
- const createComponentWithApollo = (
+ const createComponentWithApollo = ({
mountFn = shallowMount,
getPipelineDetailsHandler = jest.fn().mockResolvedValue(wrappedPipelineReturn),
- ) => {
+ props = {},
+ } = {}) => {
const requestHandlers = [[getPipelineDetails, getPipelineDetailsHandler]];
const apolloProvider = createMockApollo(requestHandlers);
- createComponent({ apolloProvider, mountFn });
+ createComponent({ apolloProvider, mountFn, props });
};
afterEach(() => {
@@ -86,34 +87,90 @@ describe('Linked Pipelines Column', () => {
await wrapper.vm.$nextTick();
};
- describe('when successful', () => {
- beforeEach(() => {
- createComponentWithApollo(mount);
+ describe('downstream', () => {
+ describe('when successful', () => {
+ beforeEach(() => {
+ createComponentWithApollo({ mountFn: mount });
+ });
+
+ it('toggles the pipeline visibility', async () => {
+ expect(findPipelineGraph().exists()).toBe(false);
+ await clickExpandButtonAndAwaitTimers();
+ expect(findPipelineGraph().exists()).toBe(true);
+ await clickExpandButton();
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
});
- it('toggles the pipeline visibility', async () => {
- expect(findPipelineGraph().exists()).toBe(false);
- await clickExpandButtonAndAwaitTimers();
- expect(findPipelineGraph().exists()).toBe(true);
- await clickExpandButton();
- expect(findPipelineGraph().exists()).toBe(false);
+ describe('on error', () => {
+ beforeEach(() => {
+ createComponentWithApollo({
+ mountFn: mount,
+ getPipelineDetailsHandler: jest.fn().mockRejectedValue(new Error('GraphQL error')),
+ });
+ });
+
+ it('emits the error', async () => {
+ await clickExpandButton();
+ expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ });
+
+ it('does not show the pipeline', async () => {
+ expect(findPipelineGraph().exists()).toBe(false);
+ await clickExpandButtonAndAwaitTimers();
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
});
});
- describe('on error', () => {
- beforeEach(() => {
- createComponentWithApollo(mount, jest.fn().mockRejectedValue(new Error('GraphQL error')));
- });
-
- it('emits the error', async () => {
- await clickExpandButton();
- expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ describe('upstream', () => {
+ const upstreamProps = {
+ columnTitle: 'Upstream',
+ /*
+ Because the IDs need to match to work, rather
+ than make new mock data, we are representing
+ the upstream pipeline with the downstream data.
+ */
+ linkedPipelines: processedPipeline.downstream,
+ type: UPSTREAM,
+ };
+
+ describe('when successful', () => {
+ beforeEach(() => {
+ createComponentWithApollo({
+ mountFn: mount,
+ props: upstreamProps,
+ });
+ });
+
+ it('toggles the pipeline visibility', async () => {
+ expect(findPipelineGraph().exists()).toBe(false);
+ await clickExpandButtonAndAwaitTimers();
+ expect(findPipelineGraph().exists()).toBe(true);
+ await clickExpandButton();
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
});
- it('does not show the pipeline', async () => {
- expect(findPipelineGraph().exists()).toBe(false);
- await clickExpandButtonAndAwaitTimers();
- expect(findPipelineGraph().exists()).toBe(false);
+ describe('on error', () => {
+ beforeEach(() => {
+ createComponentWithApollo({
+ mountFn: mount,
+ getPipelineDetailsHandler: jest.fn().mockRejectedValue(new Error('GraphQL error')),
+ props: upstreamProps,
+ });
+ });
+
+ it('emits the error', async () => {
+ await clickExpandButton();
+ expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ });
+
+ it('does not show the pipeline', async () => {
+ expect(findPipelineGraph().exists()).toBe(false);
+ await clickExpandButtonAndAwaitTimers();
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
});
});
});
diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js
index 202e25ccda3..16dc70a63a5 100644
--- a/spec/frontend/pipelines/graph/stage_column_component_spec.js
+++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js
@@ -68,6 +68,10 @@ describe('stage column component', () => {
it('should render the provided groups', () => {
expect(findAllStageColumnGroups().length).toBe(mockGroups.length);
});
+
+ it('should emit updateMeasurements event on mount', () => {
+ expect(wrapper.emitted().updateMeasurements).toHaveLength(1);
+ });
});
describe('when job notifies action is complete', () => {
diff --git a/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap
new file mode 100644
index 00000000000..cf2b66dea5f
--- /dev/null
+++ b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Links Inner component with a large number of needs matches snapshot and has expected path 1`] = `
+"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
+ <path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ <path d=\\"M202,118L52,118C82,118,82,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ <path d=\\"M222,138L62,138C92,138,92,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ <path d=\\"M212,128L72,128C102,128,102,168,132,168\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ <path d=\\"M232,148L82,148C112,148,112,178,142,178\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ </svg> </div>"
+`;
+
+exports[`Links Inner component with a parallel need matches snapshot and has expected path 1`] = `
+"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
+ <path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ </svg> </div>"
+`;
+
+exports[`Links Inner component with one need matches snapshot and has expected path 1`] = `
+"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
+ <path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
+ </svg> </div>"
+`;
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
new file mode 100644
index 00000000000..6cabe2bc8a7
--- /dev/null
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -0,0 +1,197 @@
+import { shallowMount } from '@vue/test-utils';
+import { setHTMLFixture } from 'helpers/fixtures';
+import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
+import { createJobsHash } from '~/pipelines/utils';
+import {
+ jobRect,
+ largePipelineData,
+ parallelNeedData,
+ pipelineData,
+ pipelineDataWithNoNeeds,
+ rootRect,
+} from '../pipeline_graph/mock_data';
+
+describe('Links Inner component', () => {
+ const containerId = 'pipeline-graph-container';
+ const defaultProps = {
+ containerId,
+ containerMeasurements: { width: 1019, height: 445 },
+ pipelineId: 1,
+ pipelineData: [],
+ };
+ let wrapper;
+
+ const createComponent = (props) => {
+ wrapper = shallowMount(LinksInner, {
+ propsData: { ...defaultProps, ...props },
+ });
+ };
+
+ const findLinkSvg = () => wrapper.find('#link-svg');
+ const findAllLinksPath = () => findLinkSvg().findAll('path');
+
+ // We create fixture so that each job has an empty div that represent
+ // the JobPill in the DOM. Each `JobPill` would have different coordinates,
+ // so we increment their coordinates on each iteration to simulat different positions.
+ const setFixtures = ({ stages }) => {
+ const jobs = createJobsHash(stages);
+ const arrayOfJobs = Object.keys(jobs);
+
+ const linksHtmlElements = arrayOfJobs.map((job) => {
+ return `<div id=${job}-${defaultProps.pipelineId} />`;
+ });
+
+ setHTMLFixture(`<div id="${containerId}">${linksHtmlElements.join(' ')}</div>`);
+
+ // We are mocking the clientRect data of each job and the container ID.
+ jest
+ .spyOn(document.getElementById(containerId), 'getBoundingClientRect')
+ .mockImplementation(() => rootRect);
+
+ arrayOfJobs.forEach((job, index) => {
+ jest
+ .spyOn(
+ document.getElementById(`${job}-${defaultProps.pipelineId}`),
+ 'getBoundingClientRect',
+ )
+ .mockImplementation(() => {
+ const newValue = 10 * index;
+ const { left, right, top, bottom, x, y } = jobRect;
+ return {
+ ...jobRect,
+ left: left + newValue,
+ right: right + newValue,
+ top: top + newValue,
+ bottom: bottom + newValue,
+ x: x + newValue,
+ y: y + newValue,
+ };
+ });
+ });
+ };
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('basic SVG creation', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders an SVG of the right size', () => {
+ expect(findLinkSvg().exists()).toBe(true);
+ expect(findLinkSvg().attributes('width')).toBe(
+ `${defaultProps.containerMeasurements.width}px`,
+ );
+ expect(findLinkSvg().attributes('height')).toBe(
+ `${defaultProps.containerMeasurements.height}px`,
+ );
+ });
+ });
+
+ describe('no pipeline data', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the component', () => {
+ expect(findLinkSvg().exists()).toBe(true);
+ expect(findAllLinksPath()).toHaveLength(0);
+ });
+ });
+
+ describe('pipeline data with no needs', () => {
+ beforeEach(() => {
+ createComponent({ pipelineData: pipelineDataWithNoNeeds.stages });
+ });
+
+ it('renders no links', () => {
+ expect(findLinkSvg().exists()).toBe(true);
+ expect(findAllLinksPath()).toHaveLength(0);
+ });
+ });
+
+ describe('with one need', () => {
+ beforeEach(() => {
+ setFixtures(pipelineData);
+ createComponent({ pipelineData: pipelineData.stages });
+ });
+
+ it('renders one link', () => {
+ expect(findAllLinksPath()).toHaveLength(1);
+ });
+
+ it('path does not contain NaN values', () => {
+ expect(wrapper.html()).not.toContain('NaN');
+ });
+
+ it('matches snapshot and has expected path', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('with a parallel need', () => {
+ beforeEach(() => {
+ setFixtures(parallelNeedData);
+ createComponent({ pipelineData: parallelNeedData.stages });
+ });
+
+ it('renders only one link for all the same parallel jobs', () => {
+ expect(findAllLinksPath()).toHaveLength(1);
+ });
+
+ it('path does not contain NaN values', () => {
+ expect(wrapper.html()).not.toContain('NaN');
+ });
+
+ it('matches snapshot and has expected path', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('with a large number of needs', () => {
+ beforeEach(() => {
+ setFixtures(largePipelineData);
+ createComponent({ pipelineData: largePipelineData.stages });
+ });
+
+ it('renders the correct number of links', () => {
+ expect(findAllLinksPath()).toHaveLength(5);
+ });
+
+ it('path does not contain NaN values', () => {
+ expect(wrapper.html()).not.toContain('NaN');
+ });
+
+ it('matches snapshot and has expected path', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('interactions', () => {
+ beforeEach(() => {
+ setFixtures(largePipelineData);
+ createComponent({ pipelineData: largePipelineData.stages });
+ });
+
+ it('highlight needs on hover', async () => {
+ const firstLink = findAllLinksPath().at(0);
+
+ const defaultColorClass = 'gl-stroke-gray-200';
+ const hoverColorClass = 'gl-stroke-blue-400';
+
+ expect(firstLink.classes(defaultColorClass)).toBe(true);
+ expect(firstLink.classes(hoverColorClass)).toBe(false);
+
+ // Because there is a watcher, we need to set the props after the component
+ // has mounted.
+ await wrapper.setProps({ highlightedJob: 'test_1' });
+
+ expect(firstLink.classes(defaultColorClass)).toBe(false);
+ expect(firstLink.classes(hoverColorClass)).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index 9ef5233dbce..0ff8583fbff 100644
--- a/spec/frontend/pipelines/shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -1,7 +1,7 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlAlert, GlButton } from '@gitlab/ui';
-import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
+import { mount, shallowMount } from '@vue/test-utils';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
+import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
import { generateResponse, mockPipelineResponse } from '../graph/mock_data';
describe('links layer component', () => {
diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js
index 03e385e3cc8..57d846c53c8 100644
--- a/spec/frontend/pipelines/header_component_spec.js
+++ b/spec/frontend/pipelines/header_component_spec.js
@@ -1,15 +1,15 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import HeaderComponent from '~/pipelines/components/header_component.vue';
+import cancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
+import deletePipelineMutation from '~/pipelines/graphql/mutations/delete_pipeline.mutation.graphql';
+import retryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
import {
mockCancelledPipelineHeader,
mockFailedPipelineHeader,
mockRunningPipelineHeader,
mockSuccessfulPipelineHeader,
} from './mock_data';
-import HeaderComponent from '~/pipelines/components/header_component.vue';
-import deletePipelineMutation from '~/pipelines/graphql/mutations/delete_pipeline.mutation.graphql';
-import retryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
-import cancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
describe('Pipeline details header', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/legacy_header_component_spec.js b/spec/frontend/pipelines/legacy_header_component_spec.js
deleted file mode 100644
index fb7feb8898a..00000000000
--- a/spec/frontend/pipelines/legacy_header_component_spec.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import LegacyHeaderComponent from '~/pipelines/components/legacy_header_component.vue';
-import CiHeader from '~/vue_shared/components/header_ci_component.vue';
-import eventHub from '~/pipelines/event_hub';
-
-describe('Pipeline details header', () => {
- let wrapper;
- let glModalDirective;
-
- const threeWeeksAgo = new Date();
- threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
-
- const findDeleteModal = () => wrapper.find(GlModal);
-
- const defaultProps = {
- pipeline: {
- details: {
- status: {
- group: 'failed',
- icon: 'status_failed',
- label: 'failed',
- text: 'failed',
- details_path: 'path',
- },
- },
- id: 123,
- created_at: threeWeeksAgo.toISOString(),
- user: {
- web_url: 'path',
- name: 'Foo',
- username: 'foobar',
- email: 'foo@bar.com',
- avatar_url: 'link',
- },
- retry_path: 'retry',
- cancel_path: 'cancel',
- delete_path: 'delete',
- },
- isLoading: false,
- };
-
- const createComponent = (props = {}) => {
- glModalDirective = jest.fn();
-
- wrapper = shallowMount(LegacyHeaderComponent, {
- propsData: {
- ...props,
- },
- directives: {
- glModal: {
- bind(el, { value }) {
- glModalDirective(value);
- },
- },
- },
- });
- };
-
- beforeEach(() => {
- jest.spyOn(eventHub, '$emit');
-
- createComponent(defaultProps);
- });
-
- afterEach(() => {
- eventHub.$off();
-
- wrapper.destroy();
- wrapper = null;
- });
-
- it('should render provided pipeline info', () => {
- expect(wrapper.find(CiHeader).props()).toMatchObject({
- status: defaultProps.pipeline.details.status,
- itemId: defaultProps.pipeline.id,
- time: defaultProps.pipeline.created_at,
- user: defaultProps.pipeline.user,
- });
- });
-
- describe('action buttons', () => {
- it('should not trigger eventHub when nothing happens', () => {
- expect(eventHub.$emit).not.toHaveBeenCalled();
- });
-
- it('should call postAction when retry button action is clicked', () => {
- wrapper.find('[data-testid="retryButton"]').vm.$emit('click');
-
- expect(eventHub.$emit).toHaveBeenCalledWith('headerPostAction', 'retry');
- });
-
- it('should call postAction when cancel button action is clicked', () => {
- wrapper.find('[data-testid="cancelPipeline"]').vm.$emit('click');
-
- expect(eventHub.$emit).toHaveBeenCalledWith('headerPostAction', 'cancel');
- });
-
- it('does not show delete modal', () => {
- expect(findDeleteModal()).not.toBeVisible();
- });
-
- describe('when delete button action is clicked', () => {
- it('displays delete modal', () => {
- expect(findDeleteModal().props('modalId')).toBe(wrapper.vm.$options.DELETE_MODAL_ID);
- expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.$options.DELETE_MODAL_ID);
- });
-
- it('should call delete when modal is submitted', () => {
- findDeleteModal().vm.$emit('ok');
-
- expect(eventHub.$emit).toHaveBeenCalledWith('headerDeleteAction', 'delete');
- });
- });
- });
-});
diff --git a/spec/frontend/pipelines/pipeline_graph/mock_data.js b/spec/frontend/pipelines/pipeline_graph/mock_data.js
index 7d1a7a79c7f..339aac9f349 100644
--- a/spec/frontend/pipelines/pipeline_graph/mock_data.js
+++ b/spec/frontend/pipelines/pipeline_graph/mock_data.js
@@ -1,5 +1,3 @@
-import { createUniqueLinkId } from '~/pipelines/components/graph_shared/drawing_utils';
-
export const yamlString = `stages:
- empty
- build
@@ -41,10 +39,28 @@ deploy_a:
script: echo hello
`;
-const jobId1 = createUniqueLinkId('build', 'build_1');
-const jobId2 = createUniqueLinkId('test', 'test_1');
-const jobId3 = createUniqueLinkId('test', 'test_2');
-const jobId4 = createUniqueLinkId('deploy', 'deploy_1');
+export const pipelineDataWithNoNeeds = {
+ stages: [
+ {
+ name: 'build',
+ groups: [
+ {
+ name: 'build_1',
+ jobs: [{ script: 'echo hello', stage: 'build' }],
+ },
+ ],
+ },
+ {
+ name: 'test',
+ groups: [
+ {
+ name: 'test_1',
+ jobs: [{ script: 'yarn test', stage: 'test' }],
+ },
+ ],
+ },
+ ],
+};
export const pipelineData = {
stages: [
@@ -54,7 +70,6 @@ export const pipelineData = {
{
name: 'build_1',
jobs: [{ script: 'echo hello', stage: 'build' }],
- id: jobId1,
},
],
},
@@ -64,12 +79,10 @@ export const pipelineData = {
{
name: 'test_1',
jobs: [{ script: 'yarn test', stage: 'test' }],
- id: jobId2,
},
{
name: 'test_2',
jobs: [{ script: 'yarn karma', stage: 'test' }],
- id: jobId3,
},
],
},
@@ -79,7 +92,86 @@ export const pipelineData = {
{
name: 'deploy_1',
jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_1'] }],
- id: jobId4,
+ },
+ ],
+ },
+ ],
+};
+
+export const parallelNeedData = {
+ stages: [
+ {
+ name: 'build',
+ groups: [
+ {
+ name: 'build_1',
+ parallel: 3,
+ jobs: [
+ { script: 'echo hello', stage: 'build', name: 'build_1 1/3' },
+ { script: 'echo hello', stage: 'build', name: 'build_1 2/3' },
+ { script: 'echo hello', stage: 'build', name: 'build_1 3/3' },
+ ],
+ },
+ ],
+ },
+ {
+ name: 'test',
+ groups: [
+ {
+ name: 'test_1',
+ jobs: [{ script: 'yarn test', stage: 'test', needs: ['build_1'] }],
+ },
+ ],
+ },
+ ],
+};
+
+export const largePipelineData = {
+ stages: [
+ {
+ name: 'build',
+ groups: [
+ {
+ name: 'build_1',
+ jobs: [{ script: 'echo hello', stage: 'build' }],
+ },
+ {
+ name: 'build_2',
+ jobs: [{ script: 'echo hello', stage: 'build' }],
+ },
+ {
+ name: 'build_3',
+ jobs: [{ script: 'echo hello', stage: 'build' }],
+ },
+ ],
+ },
+ {
+ name: 'test',
+ groups: [
+ {
+ name: 'test_1',
+ jobs: [{ script: 'yarn test', stage: 'test', needs: ['build_2'] }],
+ },
+ {
+ name: 'test_2',
+ jobs: [{ script: 'yarn karma', stage: 'test', needs: ['build_2'] }],
+ },
+ ],
+ },
+ {
+ name: 'deploy',
+ groups: [
+ {
+ name: 'deploy_1',
+ jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_1'] }],
+ },
+ {
+ name: 'deploy_2',
+ jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['build_3'] }],
+ },
+ {
+ name: 'deploy_3',
+ jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_2'] }],
},
],
},
@@ -94,9 +186,30 @@ export const singleStageData = {
{
name: 'build_1',
jobs: [{ script: 'echo hello', stage: 'build' }],
- id: jobId1,
},
],
},
],
};
+
+export const rootRect = {
+ bottom: 463,
+ height: 271,
+ left: 236,
+ right: 1252,
+ top: 192,
+ width: 1016,
+ x: 236,
+ y: 192,
+};
+
+export const jobRect = {
+ bottom: 312,
+ height: 24,
+ left: 308,
+ right: 428,
+ top: 288,
+ width: 120,
+ x: 308,
+ y: 288,
+};
diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
index b6b0a964383..718667fcc73 100644
--- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
-import { pipelineData, singleStageData } from './mock_data';
+import { shallowMount } from '@vue/test-utils';
import { CI_CONFIG_STATUS_INVALID, CI_CONFIG_STATUS_VALID } from '~/pipeline_editor/constants';
-import { DRAW_FAILURE, EMPTY_PIPELINE_DATA, INVALID_CI_CONFIG } from '~/pipelines/constants';
+import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue';
-import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue';
+import { DRAW_FAILURE, EMPTY_PIPELINE_DATA, INVALID_CI_CONFIG } from '~/pipelines/constants';
+import { pipelineData, singleStageData } from './mock_data';
describe('pipeline graph component', () => {
const defaultProps = { pipelineData };
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index ad8136890e6..467a97d95c7 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import pipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
describe('Pipelines Triggerer', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 47315bd42e6..44c9def99cc 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,6 +1,6 @@
+import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
import { trimText } from 'helpers/text_helper';
-import { shallowMount } from '@vue/test-utils';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
$.fn.popover = () => {};
@@ -17,6 +17,7 @@ describe('Pipeline Url Component', () => {
const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]');
+ const findTrainTag = () => wrapper.find('[data-testid="pipeline-url-train"]');
const defaultProps = {
pipeline: {
@@ -141,6 +142,7 @@ describe('Pipeline Url Component', () => {
expect(findScheduledTag().exists()).toBe(true);
expect(findScheduledTag().text()).toContain('Scheduled');
});
+
it('should render the fork badge when the pipeline was run in a fork', () => {
createComponent({
pipeline: {
@@ -152,4 +154,28 @@ describe('Pipeline Url Component', () => {
expect(findForkTag().exists()).toBe(true);
expect(findForkTag().text()).toBe('fork');
});
+
+ it('should render the train badge when the pipeline is a merge train pipeline', () => {
+ createComponent({
+ pipeline: {
+ flags: {
+ merge_train_pipeline: true,
+ },
+ },
+ });
+
+ expect(findTrainTag().text()).toContain('train');
+ });
+
+ it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
+ createComponent({
+ pipeline: {
+ flags: {
+ merge_train_pipeline: false,
+ },
+ },
+ });
+
+ expect(findTrainTag().exists()).toBe(false);
+ });
});
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index 69c1b7ce43d..1e6c9e50a7e 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -1,25 +1,29 @@
-import { shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
-import { GlButton } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
+import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
+jest.mock('~/flash');
+
describe('Pipelines Actions dropdown', () => {
let wrapper;
let mock;
- const createComponent = (actions = []) => {
- wrapper = shallowMount(PipelinesActions, {
+ const createComponent = (props, mountFn = shallowMount) => {
+ wrapper = mountFn(PipelinesActions, {
propsData: {
- actions,
+ ...props,
},
});
};
- const findAllDropdownItems = () => wrapper.findAll(GlButton);
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
const findAllCountdowns = () => wrapper.findAll(GlCountdown);
beforeEach(() => {
@@ -47,7 +51,7 @@ describe('Pipelines Actions dropdown', () => {
];
beforeEach(() => {
- createComponent(mockActions);
+ createComponent({ actions: mockActions });
});
it('renders a dropdown with the provided actions', () => {
@@ -59,16 +63,33 @@ describe('Pipelines Actions dropdown', () => {
});
describe('on click', () => {
- it('makes a request and toggles the loading state', () => {
+ beforeEach(() => {
+ createComponent({ actions: mockActions }, mount);
+ });
+
+ it('makes a request and toggles the loading state', async () => {
mock.onPost(mockActions.path).reply(200);
- wrapper.find(GlButton).vm.$emit('click');
+ findAllDropdownItems().at(0).vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+ expect(findDropdown().props('loading')).toBe(true);
+
+ await waitForPromises();
+ expect(findDropdown().props('loading')).toBe(false);
+ });
+
+ it('makes a failed request and toggles the loading state', async () => {
+ mock.onPost(mockActions.path).reply(500);
- expect(wrapper.vm.isLoading).toBe(true);
+ findAllDropdownItems().at(0).vm.$emit('click');
- return waitForPromises().then(() => {
- expect(wrapper.vm.isLoading).toBe(false);
- });
+ await wrapper.vm.$nextTick();
+ expect(findDropdown().props('loading')).toBe(true);
+
+ await waitForPromises();
+ expect(findDropdown().props('loading')).toBe(false);
+ expect(createFlash).toHaveBeenCalledTimes(1);
});
});
});
@@ -89,10 +110,10 @@ describe('Pipelines Actions dropdown', () => {
beforeEach(() => {
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
- createComponent([scheduledJobAction, expiredJobAction]);
+ createComponent({ actions: [scheduledJobAction, expiredJobAction] });
});
- it('makes post request after confirming', () => {
+ it('makes post request after confirming', async () => {
mock.onPost(scheduledJobAction.path).reply(200);
jest.spyOn(window, 'confirm').mockReturnValue(true);
@@ -100,19 +121,22 @@ describe('Pipelines Actions dropdown', () => {
expect(window.confirm).toHaveBeenCalled();
- return waitForPromises().then(() => {
- expect(mock.history.post.length).toBe(1);
- });
+ await waitForPromises();
+
+ expect(mock.history.post).toHaveLength(1);
});
- it('does not make post request if confirmation is cancelled', () => {
+ it('does not make post request if confirmation is cancelled', async () => {
mock.onPost(scheduledJobAction.path).reply(200);
jest.spyOn(window, 'confirm').mockReturnValue(false);
findAllDropdownItems().at(0).vm.$emit('click');
expect(window.confirm).toHaveBeenCalled();
- expect(mock.history.post.length).toBe(0);
+
+ await waitForPromises();
+
+ expect(mock.history.post).toHaveLength(0);
});
it('displays the remaining time in the dropdown', () => {
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index 4f4c15fd4cc..f077833ae16 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
describe('Pipelines Artifacts dropdown', () => {
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 5d82669b0b8..811303a5624 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -1,49 +1,50 @@
-import { nextTick } from 'vue';
+import { GlFilteredSearch, GlButton, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { chunk } from 'lodash';
+import { nextTick } from 'vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { GlFilteredSearch, GlButton, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
-import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
-
-import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
-import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
import BlankState from '~/pipelines/components/pipelines_list/blank_state.vue';
-import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
-
+import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
+import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
+import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
+import { RAW_TEXT_WARNING } from '~/pipelines/constants';
import Store from '~/pipelines/stores/pipelines_store';
+import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
-import { RAW_TEXT_WARNING } from '~/pipelines/constants';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
-describe('Pipelines', () => {
- const jsonFixtureName = 'pipelines/pipelines.json';
+const mockProjectPath = 'twitter/flight';
+const mockProjectId = '21';
+const mockPipelinesEndpoint = `/${mockProjectPath}/pipelines.json`;
+const mockPipelinesResponse = getJSONFixture('pipelines/pipelines.json');
+const mockPipelinesIds = mockPipelinesResponse.pipelines.map(({ id }) => id);
- preloadFixtures(jsonFixtureName);
-
- let pipelines;
+describe('Pipelines', () => {
let wrapper;
let mock;
+ let origWindowLocation;
const paths = {
- endpoint: 'twitter/flight/pipelines.json',
autoDevopsHelpPath: '/help/topics/autodevops/index.md',
helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
ciLintPath: '/ci/lint',
- resetCachePath: '/twitter/flight/settings/ci_cd/reset_cache',
- newPipelinePath: '/twitter/flight/pipelines/new',
+ resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`,
+ newPipelinePath: `${mockProjectPath}/pipelines/new`,
};
const noPermissions = {
- endpoint: 'twitter/flight/pipelines.json',
autoDevopsHelpPath: '/help/topics/autodevops/index.md',
helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
@@ -57,101 +58,140 @@ describe('Pipelines', () => {
...paths,
};
- const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
- const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`);
- const findNavigationTabs = () => wrapper.find(NavigationTabs);
- const findNavigationControls = () => wrapper.find(NavigationControls);
- const findTab = (tab) => findByTestId(`pipelines-tab-${tab}`);
-
- const findRunPipelineButton = () => findByTestId('run-pipeline-button');
- const findCiLintButton = () => findByTestId('ci-lint-button');
- const findCleanCacheButton = () => findByTestId('clear-cache-button');
-
- const findEmptyState = () => wrapper.find(EmptyState);
- const findBlankState = () => wrapper.find(BlankState);
- const findStagesDropdown = () => wrapper.find('.js-builds-dropdown-button');
-
- const findTablePagination = () => wrapper.find(TablePagination);
+ const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
+ const findNavigationTabs = () => wrapper.findComponent(NavigationTabs);
+ const findNavigationControls = () => wrapper.findComponent(NavigationControls);
+ const findPipelinesTable = () => wrapper.findComponent(PipelinesTableComponent);
+ const findEmptyState = () => wrapper.findComponent(EmptyState);
+ const findBlankState = () => wrapper.findComponent(BlankState);
+ const findTablePagination = () => wrapper.findComponent(TablePagination);
+
+ const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`);
+ const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
+ const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
+ const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button');
+ const findStagesDropdown = () => wrapper.findByTestId('mini-pipeline-graph-dropdown-toggle');
+ const findPipelineUrlLinks = () => wrapper.findAll('[data-testid="pipeline-url-link"]');
const createComponent = (props = defaultProps) => {
- wrapper = mount(PipelinesComponent, {
- propsData: {
- store: new Store(),
- projectId: '21',
- params: {},
- ...props,
- },
- });
+ wrapper = extendedWrapper(
+ mount(PipelinesComponent, {
+ propsData: {
+ store: new Store(),
+ projectId: mockProjectId,
+ endpoint: mockPipelinesEndpoint,
+ params: {},
+ ...props,
+ },
+ }),
+ );
};
- beforeEach(() => {
+ beforeAll(() => {
+ origWindowLocation = window.location;
delete window.location;
+ window.location = { search: '' };
+ });
+
+ afterAll(() => {
+ window.location = origWindowLocation;
});
beforeEach(() => {
- window.location = { search: '' };
mock = new MockAdapter(axios);
- pipelines = getJSONFixture(jsonFixtureName);
+ jest.spyOn(window.history, 'pushState');
jest.spyOn(Api, 'projectUsers').mockResolvedValue(users);
jest.spyOn(Api, 'branches').mockResolvedValue({ data: branches });
});
afterEach(() => {
wrapper.destroy();
- mock.restore();
+ mock.reset();
+ window.history.pushState.mockReset();
});
- describe('With permission', () => {
- describe('With pipelines in main tab', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
- createComponent();
- return waitForPromises();
- });
+ describe('when pipelines are not yet loaded', () => {
+ beforeEach(async () => {
+ createComponent();
+ await nextTick();
+ });
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
- });
+ it('shows loading state when the app is loading', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
- it('renders Run Pipeline link', () => {
- expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
+ it('does not display tabs when the first request has not yet been made', () => {
+ expect(findNavigationTabs().exists()).toBe(false);
+ });
+
+ it('does not display buttons', () => {
+ expect(findNavigationControls().exists()).toBe(false);
+ });
+ });
+
+ describe('when there are pipelines in the project', () => {
+ beforeEach(() => {
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } })
+ .reply(200, mockPipelinesResponse);
+ });
+
+ describe('when user has no permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });
+ await waitForPromises();
});
- it('renders CI Lint link', () => {
- expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
+ it('renders "All" tab with count different from "0"', () => {
+ expect(findTab('all').text()).toMatchInterpolatedText('All 3');
});
- it('renders Clear Runner Cache button', () => {
- expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
+ it('does not render buttons', () => {
+ expect(findNavigationControls().exists()).toBe(false);
+
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
});
- it('renders pipelines table', () => {
- expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
- pipelines.pipelines.length + 1,
- );
+ it('renders pipelines in a table', () => {
+ expect(findPipelinesTable().exists()).toBe(true);
+
+ expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
+ expect(findPipelineUrlLinks().at(2).text()).toBe(`#${mockPipelinesIds[2]}`);
});
});
- describe('Without pipelines on main tab with CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
- });
-
+ describe('when user has permissions', () => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
+ });
- return waitForPromises();
+ it('should set up navigation tabs', () => {
+ expect(findNavigationTabs().props('tabs')).toEqual([
+ { name: 'All', scope: 'all', count: '3', isActive: true },
+ { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
+ { name: 'Branches', scope: 'branches', isActive: false },
+ { name: 'Tags', scope: 'tags', isActive: false },
+ ]);
});
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
+ it('renders "All" tab with count different from "0"', () => {
+ expect(findTab('all').text()).toMatchInterpolatedText('All 3');
+ });
+
+ it('should render other navigation tabs', () => {
+ expect(findTab('finished').text()).toBe('Finished');
+ expect(findTab('branches').text()).toBe('Branches');
+ expect(findTab('tags').text()).toBe('Tags');
+ });
+
+ it('shows navigation controls', () => {
+ expect(findNavigationControls().exists()).toBe(true);
});
it('renders Run Pipeline link', () => {
@@ -166,549 +206,513 @@ describe('Pipelines', () => {
expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
});
- it('renders tab empty state', () => {
- expect(findBlankState().text()).toBe('There are currently no pipelines.');
- });
-
- it('renders tab empty state finished scope', () => {
- wrapper.vm.scope = 'finished';
+ it('renders pipelines in a table', () => {
+ expect(findPipelinesTable().exists()).toBe(true);
- return nextTick().then(() => {
- expect(findBlankState().text()).toBe('There are currently no finished pipelines.');
- });
+ expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
+ expect(findPipelineUrlLinks().at(2).text()).toBe(`#${mockPipelinesIds[2]}`);
});
- });
-
- describe('Without pipelines nor CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
- });
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
+ describe('when user goes to a tab', () => {
+ const goToTab = (tab) => {
+ findNavigationTabs().vm.$emit('onChangeTab', tab);
+ };
- return waitForPromises();
- });
+ describe('when the scope in the tab has pipelines', () => {
+ const mockFinishedPipeline = mockPipelinesResponse.pipelines[0];
- it('renders empty state', () => {
- expect(findEmptyState().find('h4').text()).toBe('Build with confidence');
- expect(findEmptyState().find(GlButton).attributes('href')).toBe(paths.helpPagePath);
- });
+ beforeEach(async () => {
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } })
+ .reply(200, {
+ pipelines: [mockFinishedPipeline],
+ count: mockPipelinesResponse.count,
+ });
- it('does not render tabs nor buttons', () => {
- expect(findTab('all').exists()).toBe(false);
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
- });
+ goToTab('finished');
- describe('When API returns error', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(500, {});
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
+ await waitForPromises();
+ });
- return waitForPromises();
- });
+ it('should filter pipelines', async () => {
+ expect(findPipelinesTable().exists()).toBe(true);
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
- });
+ expect(findPipelineUrlLinks()).toHaveLength(1);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFinishedPipeline.id}`);
+ });
- it('renders buttons', () => {
- expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?scope=finished&page=1`,
+ );
+ });
+ });
- expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
- expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
- });
+ describe('when the scope in the tab is empty', () => {
+ beforeEach(async () => {
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'branches', page: '1' } })
+ .reply(200, {
+ pipelines: [],
+ count: mockPipelinesResponse.count,
+ });
- it('renders error state', () => {
- expect(findBlankState().text()).toContain('There was an error fetching the pipelines.');
- });
- });
- });
+ goToTab('branches');
- describe('Without permission', () => {
- describe('With pipelines in main tab', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+ await waitForPromises();
+ });
- createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });
+ it('should filter pipelines', async () => {
+ expect(findBlankState().text()).toBe('There are currently no pipelines.');
+ });
- return waitForPromises();
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?scope=branches&page=1`,
+ );
+ });
+ });
});
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
- });
+ describe('when user triggers a filtered search', () => {
+ const mockFilteredPipeline = mockPipelinesResponse.pipelines[1];
- it('does not render buttons', () => {
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
+ let expectedParams;
- it('renders pipelines table', () => {
- expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
- pipelines.pipelines.length + 1,
- );
- });
- });
+ beforeEach(async () => {
+ expectedParams = {
+ page: '1',
+ scope: 'all',
+ username: 'root',
+ ref: 'master',
+ status: 'pending',
+ };
+
+ mock
+ .onGet(mockPipelinesEndpoint, {
+ params: expectedParams,
+ })
+ .replyOnce(200, {
+ pipelines: [mockFilteredPipeline],
+ count: mockPipelinesResponse.count,
+ });
- describe('Without pipelines on main tab with CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
+ findFilteredSearch().vm.$emit('submit', mockSearch);
+
+ await waitForPromises();
});
- createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });
+ it('requests data with query params on filter submit', async () => {
+ expect(mock.history.get[1].params).toEqual(expectedParams);
+ });
- return waitForPromises();
- });
+ it('renders filtered pipelines', async () => {
+ expect(findPipelineUrlLinks()).toHaveLength(1);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFilteredPipeline.id}`);
+ });
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?page=1&scope=all&username=root&ref=master&status=pending`,
+ );
+ });
});
- it('does not render buttons', () => {
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
+ describe('when user triggers a filtered search with raw text', () => {
+ beforeEach(async () => {
+ findFilteredSearch().vm.$emit('submit', ['rawText']);
- it('renders tab empty state', () => {
- expect(wrapper.find('.empty-state h4').text()).toBe('There are currently no pipelines.');
- });
- });
+ await waitForPromises();
+ });
- describe('Without pipelines nor CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
+ it('requests data with query params on filter submit', async () => {
+ expect(mock.history.get[1].params).toEqual({ page: '1', scope: 'all' });
});
- createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });
+ it('displays a warning message if raw text search is used', () => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(RAW_TEXT_WARNING, 'warning');
+ });
- return waitForPromises();
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?page=1&scope=all`,
+ );
+ });
});
+ });
+ });
- it('renders empty state without button to set CI', () => {
- expect(findEmptyState().text()).toBe(
- 'This project is not currently set up to run pipelines.',
- );
+ describe('when there are multiple pages of pipelines', () => {
+ const mockPageSize = 2;
+ const mockPageHeaders = ({ page = 1 } = {}) => {
+ return {
+ 'X-PER-PAGE': `${mockPageSize}`,
+ 'X-PREV-PAGE': `${page - 1}`,
+ 'X-PAGE': `${page}`,
+ 'X-NEXT-PAGE': `${page + 1}`,
+ };
+ };
+ const [firstPage, secondPage] = chunk(mockPipelinesResponse.pipelines, mockPageSize);
+
+ const goToPage = (page) => {
+ findTablePagination().find(GlPagination).vm.$emit('input', page);
+ };
+
+ beforeEach(async () => {
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }).reply(
+ 200,
+ {
+ pipelines: firstPage,
+ count: mockPipelinesResponse.count,
+ },
+ mockPageHeaders({ page: 1 }),
+ );
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '2' } }).reply(
+ 200,
+ {
+ pipelines: secondPage,
+ count: mockPipelinesResponse.count,
+ },
+ mockPageHeaders({ page: 2 }),
+ );
- expect(findEmptyState().find(GlButton).exists()).toBeFalsy();
- });
+ createComponent();
- it('does not render tabs or buttons', () => {
- expect(findTab('all').exists()).toBe(false);
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
+ await waitForPromises();
});
- describe('When API returns error', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(500, {});
-
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...noPermissions });
+ it('shows the first page of pipelines', () => {
+ expect(findPipelineUrlLinks()).toHaveLength(firstPage.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${firstPage[0].id}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${firstPage[1].id}`);
+ });
- return waitForPromises();
- });
+ it('should not update browser bar', () => {
+ expect(window.history.pushState).not.toHaveBeenCalled();
+ });
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
+ describe('when user goes to next page', () => {
+ beforeEach(async () => {
+ goToPage(2);
+ await waitForPromises();
});
- it('does not renders buttons', () => {
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
+ it('should update page and keep scope the same scope', () => {
+ expect(findPipelineUrlLinks()).toHaveLength(secondPage.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${secondPage[0].id}`);
});
- it('renders error state', () => {
- expect(wrapper.find('.empty-state').text()).toContain(
- 'There was an error fetching the pipelines.',
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?page=2&scope=all`,
);
});
});
});
- describe('successful request', () => {
- describe('with pipelines', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+ describe('when pipelines can be polled', () => {
+ beforeEach(() => {
+ const emptyResponse = {
+ pipelines: [],
+ count: { all: '0' },
+ };
+ // Mock no pipelines in the first attempt
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } })
+ .replyOnce(200, emptyResponse, {
+ 'POLL-INTERVAL': 100,
+ });
+ // Mock pipelines in the next attempt
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } })
+ .reply(200, mockPipelinesResponse, {
+ 'POLL-INTERVAL': 100,
+ });
+ });
+
+ describe('data is loaded for the first time', () => {
+ beforeEach(async () => {
createComponent();
- return waitForPromises();
+ await waitForPromises();
});
- it('should render table', () => {
- expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
- pipelines.pipelines.length + 1,
- );
+ it('shows tabs', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
});
- it('should set up navigation tabs', () => {
- expect(findNavigationTabs().props('tabs')).toEqual([
- { name: 'All', scope: 'all', count: '3', isActive: true },
- { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
- { name: 'Branches', scope: 'branches', isActive: false },
- { name: 'Tags', scope: 'tags', isActive: false },
- ]);
+ it('should update page and keep scope the same scope', () => {
+ expect(findPipelineUrlLinks()).toHaveLength(0);
});
- it('should render navigation tabs', () => {
- expect(findTab('all').html()).toContain('All');
- expect(findTab('finished').text()).toContain('Finished');
- expect(findTab('branches').text()).toContain('Branches');
- expect(findTab('tags').text()).toContain('Tags');
- });
-
- it('should make an API request when using tabs', () => {
- createComponent({ hasGitlabCi: true, canCreatePipeline: true, ...paths });
- jest.spyOn(wrapper.vm.service, 'getPipelines');
-
- return waitForPromises().then(() => {
- findTab('finished').trigger('click');
-
- expect(wrapper.vm.service.getPipelines).toHaveBeenCalledWith({
- scope: 'finished',
- page: '1',
- });
+ describe('data is loaded for a second time', () => {
+ beforeEach(async () => {
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
});
- });
-
- describe('with pagination', () => {
- it('should make an API request when using pagination', () => {
- createComponent({ hasGitlabCi: true, canCreatePipeline: true, ...paths });
- jest.spyOn(wrapper.vm.service, 'getPipelines');
- return waitForPromises()
- .then(() => {
- // Mock pagination
- wrapper.vm.store.state.pageInfo = {
- page: 1,
- total: 10,
- perPage: 2,
- nextPage: 2,
- totalPages: 5,
- };
+ it('shows tabs', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
+ });
- return nextTick();
- })
- .then(() => {
- wrapper.find('.next-page-item').trigger('click');
- expect(wrapper.vm.service.getPipelines).toHaveBeenCalledWith({
- scope: 'all',
- page: '2',
- });
- });
+ it('is loading after a time', async () => {
+ expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
+ expect(findPipelineUrlLinks().at(2).text()).toBe(`#${mockPipelinesIds[2]}`);
});
});
});
});
- describe('User Interaction', () => {
- let updateContentMock;
-
+ describe('when no pipelines exist', () => {
beforeEach(() => {
- jest.spyOn(window.history, 'pushState').mockImplementation(() => null);
- });
-
- beforeEach(() => {
- mock.onGet(paths.endpoint).reply(200, pipelines);
- createComponent();
-
- updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');
-
- return waitForPromises();
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }).reply(200, {
+ pipelines: [],
+ count: { all: '0' },
+ });
});
- describe('when user changes tabs', () => {
- it('should set page to 1', () => {
- findNavigationTabs().vm.$emit('onChangeTab', 'running');
+ describe('when CI is enabled and user has permissions', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
- expect(updateContentMock).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+ it('renders tab with count of "0"', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
+ expect(findTab('all').text()).toMatchInterpolatedText('All 0');
});
- });
- describe('when user changes page', () => {
- it('should update page and keep scope', () => {
- findTablePagination().vm.change(4);
+ it('renders Run Pipeline link', () => {
+ expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
+ });
- expect(updateContentMock).toHaveBeenCalledWith({ scope: wrapper.vm.scope, page: '4' });
+ it('renders CI Lint link', () => {
+ expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
});
- });
- describe('updates results when a staged is clicked', () => {
- beforeEach(() => {
- const copyPipeline = { ...pipelineWithStages };
- copyPipeline.id += 1;
- mock
- .onGet('twitter/flight/pipelines.json')
- .reply(
- 200,
- {
- pipelines: [pipelineWithStages],
- count: {
- all: 1,
- finished: 1,
- pending: 0,
- running: 0,
- },
- },
- {
- 'POLL-INTERVAL': 100,
- },
- )
- .onGet(pipelineWithStages.details.stages[0].dropdown_path)
- .reply(200, stageReply);
+ it('renders Clear Runner Cache button', () => {
+ expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
+ });
- createComponent();
+ it('renders empty state', () => {
+ expect(findBlankState().text()).toBe('There are currently no pipelines.');
});
- describe('when a request is being made', () => {
- it('stops polling, cancels the request, & restarts polling', () => {
- const stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
- const restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
- const cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
-
- return waitForPromises()
- .then(() => {
- wrapper.vm.isMakingRequest = true;
- findStagesDropdown().trigger('click');
- })
- .then(() => {
- expect(cancelMock).toHaveBeenCalled();
- expect(stopMock).toHaveBeenCalled();
- expect(restartMock).toHaveBeenCalled();
- });
+ it('renders tab empty state finished scope', async () => {
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, {
+ pipelines: [],
+ count: { all: '0' },
});
- });
- describe('when no request is being made', () => {
- it('stops polling & restarts polling', () => {
- const stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
- const restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+ findNavigationTabs().vm.$emit('onChangeTab', 'finished');
- return waitForPromises()
- .then(() => {
- findStagesDropdown().trigger('click');
- expect(stopMock).toHaveBeenCalled();
- })
- .then(() => {
- expect(restartMock).toHaveBeenCalled();
- });
- });
- });
- });
- });
+ await waitForPromises();
- describe('Rendered content', () => {
- beforeEach(() => {
- createComponent();
+ expect(findBlankState().text()).toBe('There are currently no finished pipelines.');
+ });
});
- describe('displays different content', () => {
- it('shows loading state when the app is loading', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ describe('when CI is not enabled and user has permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
+ await waitForPromises();
});
- it('shows error state when app has error', () => {
- wrapper.vm.hasError = true;
- wrapper.vm.isLoading = false;
-
- return nextTick().then(() => {
- expect(findBlankState().props('message')).toBe(
- 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
- );
- });
+ it('renders empty state', () => {
+ expect(findEmptyState().find('[data-testid="header-text"]').text()).toBe(
+ 'Build with confidence',
+ );
+ expect(findEmptyState().find('[data-testid="info-text"]').text()).toContain(
+ 'GitLab CI/CD can automatically build, test, and deploy your code.',
+ );
+ expect(findEmptyState().find(GlButton).text()).toBe('Get started with CI/CD');
+ expect(findEmptyState().find(GlButton).attributes('href')).toBe(paths.helpPagePath);
});
- it('shows table list when app has pipelines', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.hasError = false;
- wrapper.vm.state.pipelines = pipelines.pipelines;
-
- return nextTick().then(() => {
- expect(wrapper.find(PipelinesTableComponent).exists()).toBe(true);
- });
+ it('does not render tabs nor buttons', () => {
+ expect(findNavigationTabs().exists()).toBe(false);
+ expect(findTab('all').exists()).toBe(false);
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
});
+ });
- it('shows empty tab when app does not have pipelines but project has pipelines', () => {
- wrapper.vm.state.count.all = 10;
- wrapper.vm.isLoading = false;
-
- return nextTick().then(() => {
- expect(findBlankState().exists()).toBe(true);
- expect(findBlankState().props('message')).toBe('There are currently no pipelines.');
- });
+ describe('when CI is not enabled and user has no permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });
+ await waitForPromises();
});
- it('shows empty tab when project has CI', () => {
- wrapper.vm.isLoading = false;
+ it('renders empty state without button to set CI', () => {
+ expect(findEmptyState().text()).toBe(
+ 'This project is not currently set up to run pipelines.',
+ );
- return nextTick().then(() => {
- expect(findBlankState().exists()).toBe(true);
- expect(findBlankState().props('message')).toBe('There are currently no pipelines.');
- });
+ expect(findEmptyState().find(GlButton).exists()).toBe(false);
});
- it('shows empty state when project does not have pipelines nor CI', () => {
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
-
- wrapper.vm.isLoading = false;
-
- return nextTick().then(() => {
- expect(wrapper.find(EmptyState).exists()).toBe(true);
- });
+ it('does not render tabs or buttons', () => {
+ expect(findTab('all').exists()).toBe(false);
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
});
});
- describe('displays tabs', () => {
- it('returns true when state is loading & has already made the first request', () => {
- wrapper.vm.isLoading = true;
- wrapper.vm.hasMadeRequest = true;
+ describe('when CI is enabled and user has no permissions', () => {
+ beforeEach(() => {
+ createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
+ return waitForPromises();
});
- it('returns true when state is tableList & has already made the first request', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.state.pipelines = pipelines.pipelines;
- wrapper.vm.hasMadeRequest = true;
-
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
+ it('renders tab with count of "0"', () => {
+ expect(findTab('all').text()).toMatchInterpolatedText('All 0');
});
- it('returns true when state is error & has already made the first request', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.hasError = true;
- wrapper.vm.hasMadeRequest = true;
+ it('does not render buttons', () => {
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
+ });
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
+ it('renders empty state', () => {
+ expect(findBlankState().text()).toBe('There are currently no pipelines.');
});
+ });
+ });
- it('returns true when state is empty tab & has already made the first request', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.state.count.all = 10;
- wrapper.vm.hasMadeRequest = true;
+ describe('when a pipeline with stages exists', () => {
+ describe('updates results when a staged is clicked', () => {
+ let stopMock;
+ let restartMock;
+ let cancelMock;
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
- });
+ beforeEach(() => {
+ mock.onGet(mockPipelinesEndpoint, { scope: 'all', page: '1' }).reply(
+ 200,
+ {
+ pipelines: [pipelineWithStages],
+ count: { all: '1' },
+ },
+ {
+ 'POLL-INTERVAL': 100,
+ },
+ );
+ mock.onGet(pipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply);
- it('returns false when has not made first request', () => {
- wrapper.vm.hasMadeRequest = false;
+ createComponent();
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(false);
- });
+ stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
+ restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
+ cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
});
- it('returns false when state is empty state', () => {
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
-
- wrapper.vm.isLoading = false;
- wrapper.vm.hasMadeRequest = true;
+ describe('when a request is being made', () => {
+ beforeEach(async () => {
+ mock.onGet(mockPipelinesEndpoint).reply(200, mockPipelinesResponse);
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(false);
+ await waitForPromises();
});
- });
- });
- describe('displays buttons', () => {
- it('returns true when it has paths & has made the first request', () => {
- wrapper.vm.hasMadeRequest = true;
+ it('stops polling, cancels the request, & restarts polling', async () => {
+ // Mock init a polling cycle
+ wrapper.vm.poll.options.notificationCallback(true);
+
+ findStagesDropdown().trigger('click');
- return nextTick().then(() => {
- expect(findNavigationControls().exists()).toBe(true);
+ await waitForPromises();
+
+ expect(cancelMock).toHaveBeenCalled();
+ expect(stopMock).toHaveBeenCalled();
+ expect(restartMock).toHaveBeenCalled();
});
- });
- it('returns false when it has not made the first request', () => {
- wrapper.vm.hasMadeRequest = false;
+ it('stops polling & restarts polling', async () => {
+ findStagesDropdown().trigger('click');
- return nextTick().then(() => {
- expect(findNavigationControls().exists()).toBe(false);
+ expect(cancelMock).not.toHaveBeenCalled();
+ expect(stopMock).toHaveBeenCalled();
+ expect(restartMock).toHaveBeenCalled();
});
});
});
});
- describe('Pipeline filters', () => {
- let updateContentMock;
-
- beforeEach(() => {
- mock.onGet(paths.endpoint).reply(200, pipelines);
- createComponent();
+ describe('when pipelines cannot be loaded', () => {
+ beforeEach(async () => {
+ mock.onGet(mockPipelinesEndpoint).reply(500, {});
+ });
- updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');
+ describe('when user has no permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...noPermissions });
- return waitForPromises();
- });
+ await waitForPromises();
+ });
- it('updates request data and query params on filter submit', async () => {
- const expectedQueryParams = {
- page: '1',
- scope: 'all',
- username: 'root',
- ref: 'master',
- status: 'pending',
- };
+ it('renders tabs', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
+ expect(findTab('all').text()).toBe('All');
+ });
- findFilteredSearch().vm.$emit('submit', mockSearch);
- await nextTick();
+ it('does not render buttons', () => {
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
+ });
- expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
- expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
+ it('shows error state', () => {
+ expect(findBlankState().text()).toBe(
+ 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
+ );
+ });
});
- it('does not add query params if raw text search is used', async () => {
- const expectedQueryParams = { page: '1', scope: 'all' };
+ describe('when user has permissions', () => {
+ beforeEach(async () => {
+ createComponent();
- findFilteredSearch().vm.$emit('submit', ['rawText']);
- await nextTick();
+ await waitForPromises();
+ });
- expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
- expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
- });
+ it('renders tabs', () => {
+ expect(findTab('all').text()).toBe('All');
+ });
+
+ it('renders buttons', () => {
+ expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
- it('displays a warning message if raw text search is used', () => {
- findFilteredSearch().vm.$emit('submit', ['rawText']);
+ expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
+ expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
+ });
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(RAW_TEXT_WARNING, 'warning');
+ it('shows error state', () => {
+ expect(findBlankState().text()).toBe(
+ 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
+ );
+ });
});
});
});
diff --git a/spec/frontend/pipelines/pipelines_table_row_spec.js b/spec/frontend/pipelines/pipelines_table_row_spec.js
index 9cdd24b2ab5..660651547fc 100644
--- a/spec/frontend/pipelines/pipelines_table_row_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_row_spec.js
@@ -155,7 +155,9 @@ describe('Pipelines Table Row', () => {
it('should render an icon for each stage', () => {
expect(
- wrapper.findAll('.table-section:nth-child(4) .js-builds-dropdown-button').length,
+ wrapper.findAll(
+ '.table-section:nth-child(4) [data-testid="mini-pipeline-graph-dropdown-toggle"]',
+ ).length,
).toEqual(pipeline.details.stages.length);
});
});
diff --git a/spec/frontend/pipelines/stage_spec.js b/spec/frontend/pipelines/stage_spec.js
index e4782a1dab1..87b43558252 100644
--- a/spec/frontend/pipelines/stage_spec.js
+++ b/spec/frontend/pipelines/stage_spec.js
@@ -1,6 +1,8 @@
import 'bootstrap/js/dist/dropdown';
+import { GlDropdown } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import StageComponent from '~/pipelines/components/pipelines_list/stage.vue';
import eventHub from '~/pipelines/event_hub';
@@ -9,6 +11,7 @@ import { stageReply } from './mock_data';
describe('Pipelines stage component', () => {
let wrapper;
let mock;
+ let glFeatures;
const defaultProps = {
stage: {
@@ -22,8 +25,6 @@ describe('Pipelines stage component', () => {
updateDropdown: false,
};
- const isDropdownOpen = () => wrapper.classes('show');
-
const createComponent = (props = {}) => {
wrapper = mount(StageComponent, {
attachTo: document.body,
@@ -31,110 +32,265 @@ describe('Pipelines stage component', () => {
...defaultProps,
...props,
},
+ provide: {
+ glFeatures,
+ },
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
+ jest.spyOn(eventHub, '$emit');
+ glFeatures = {};
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ eventHub.$emit.mockRestore();
mock.restore();
});
- describe('default', () => {
- beforeEach(() => {
- createComponent();
+ describe('when ci_mini_pipeline_gl_dropdown feature flag is disabled', () => {
+ const isDropdownOpen = () => wrapper.classes('show');
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should render a dropdown with the status icon', () => {
+ expect(wrapper.attributes('class')).toEqual('dropdown');
+ expect(wrapper.find('svg').exists()).toBe(true);
+ expect(wrapper.find('button').attributes('data-toggle')).toEqual('dropdown');
+ });
});
- it('should render a dropdown with the status icon', () => {
- expect(wrapper.attributes('class')).toEqual('dropdown');
- expect(wrapper.find('svg').exists()).toBe(true);
- expect(wrapper.find('button').attributes('data-toggle')).toEqual('dropdown');
+ describe('with successful request', () => {
+ beforeEach(() => {
+ mock.onGet('path.json').reply(200, stageReply);
+ createComponent();
+ });
+
+ it('should render the received data and emit `clickedDropdown` event', async () => {
+ wrapper.find('button').trigger('click');
+
+ await axios.waitForAll();
+ expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
+ stageReply.latest_statuses[0].name,
+ );
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
+ });
});
- });
- describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet('path.json').reply(200, stageReply);
+ it('when request fails should close the dropdown', async () => {
+ mock.onGet('path.json').reply(500);
createComponent();
- });
+ wrapper.find({ ref: 'dropdown' }).trigger('click');
- it('should render the received data and emit `clickedDropdown` event', async () => {
- jest.spyOn(eventHub, '$emit');
- wrapper.find('button').trigger('click');
+ expect(isDropdownOpen()).toBe(true);
+ wrapper.find('button').trigger('click');
await axios.waitForAll();
- expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
- stageReply.latest_statuses[0].name,
- );
- expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
+ expect(isDropdownOpen()).toBe(false);
});
- });
- it('when request fails should close the dropdown', async () => {
- mock.onGet('path.json').reply(500);
- createComponent();
- wrapper.find({ ref: 'dropdown' }).trigger('click');
- expect(isDropdownOpen()).toBe(true);
+ describe('update endpoint correctly', () => {
+ beforeEach(() => {
+ const copyStage = { ...stageReply };
+ copyStage.latest_statuses[0].name = 'this is the updated content';
+ mock.onGet('bar.json').reply(200, copyStage);
+ createComponent({
+ stage: {
+ status: {
+ group: 'running',
+ icon: 'status_running',
+ title: 'running',
+ },
+ dropdown_path: 'bar.json',
+ },
+ });
+ return axios.waitForAll();
+ });
+
+ it('should update the stage to request the new endpoint provided', async () => {
+ wrapper.find('button').trigger('click');
+ await axios.waitForAll();
+
+ expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
+ 'this is the updated content',
+ );
+ });
+ });
+
+ describe('pipelineActionRequestComplete', () => {
+ beforeEach(() => {
+ mock.onGet('path.json').reply(200, stageReply);
+ mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
+ });
+
+ const clickCiAction = async () => {
+ wrapper.find('button').trigger('click');
+ await axios.waitForAll();
+
+ wrapper.find('.js-ci-action').trigger('click');
+ await axios.waitForAll();
+ };
+
+ describe('within pipeline table', () => {
+ it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
+ createComponent({ type: 'PIPELINES_TABLE' });
+
+ await clickCiAction();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ });
+ });
+
+ describe('in MR widget', () => {
+ beforeEach(() => {
+ jest.spyOn($.fn, 'dropdown');
+ });
- wrapper.find('button').trigger('click');
- await axios.waitForAll();
+ it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
+ createComponent();
- expect(isDropdownOpen()).toBe(false);
+ await clickCiAction();
+
+ expect($.fn.dropdown).toHaveBeenCalledWith('toggle');
+ });
+ });
+ });
});
- describe('update endpoint correctly', () => {
+ describe('when ci_mini_pipeline_gl_dropdown feature flag is enabled', () => {
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findDropdownToggle = () => wrapper.find('button.gl-dropdown-toggle');
+ const findDropdownMenu = () =>
+ wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]');
+ const findCiActionBtn = () => wrapper.find('.js-ci-action');
+
+ const openGlDropdown = () => {
+ findDropdownToggle().trigger('click');
+ return new Promise((resolve) => {
+ wrapper.vm.$root.$on('bv::dropdown::show', resolve);
+ });
+ };
+
beforeEach(() => {
- const copyStage = { ...stageReply };
- copyStage.latest_statuses[0].name = 'this is the updated content';
- mock.onGet('bar.json').reply(200, copyStage);
- createComponent({
- stage: {
- status: {
- group: 'running',
- icon: 'status_running',
- title: 'running',
- },
- dropdown_path: 'bar.json',
- },
+ glFeatures = { ciMiniPipelineGlDropdown: true };
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should render a dropdown with the status icon', () => {
+ expect(findDropdown().exists()).toBe(true);
+ expect(findDropdownToggle().classes('gl-dropdown-toggle')).toEqual(true);
+ expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true);
});
- return axios.waitForAll();
});
- it('should update the stage to request the new endpoint provided', async () => {
- wrapper.find('button').trigger('click');
+ describe('with successful request', () => {
+ beforeEach(() => {
+ mock.onGet('path.json').reply(200, stageReply);
+ createComponent();
+ });
+
+ it('should render the received data and emit `clickedDropdown` event', async () => {
+ await openGlDropdown();
+ await axios.waitForAll();
+
+ expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
+ expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
+ });
+ });
+
+ it('when request fails should close the dropdown', async () => {
+ mock.onGet('path.json').reply(500);
+
+ createComponent();
+
+ await openGlDropdown();
await axios.waitForAll();
- expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
- 'this is the updated content',
- );
+ expect(findDropdown().classes('show')).toBe(false);
});
- });
- describe('pipelineActionRequestComplete', () => {
- beforeEach(() => {
- mock.onGet('path.json').reply(200, stageReply);
- mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
+ describe('update endpoint correctly', () => {
+ beforeEach(async () => {
+ const copyStage = { ...stageReply };
+ copyStage.latest_statuses[0].name = 'this is the updated content';
+ mock.onGet('bar.json').reply(200, copyStage);
+ createComponent({
+ stage: {
+ status: {
+ group: 'running',
+ icon: 'status_running',
+ title: 'running',
+ },
+ dropdown_path: 'bar.json',
+ },
+ });
+ await axios.waitForAll();
+ });
- createComponent({ type: 'PIPELINES_TABLE' });
+ it('should update the stage to request the new endpoint provided', async () => {
+ await openGlDropdown();
+ await axios.waitForAll();
+
+ expect(findDropdownMenu().text()).toContain('this is the updated content');
+ });
});
- describe('within pipeline table', () => {
- it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
- jest.spyOn(eventHub, '$emit');
+ describe('pipelineActionRequestComplete', () => {
+ beforeEach(() => {
+ mock.onGet('path.json').reply(200, stageReply);
+ mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
+ });
- wrapper.find('button').trigger('click');
+ const clickCiAction = async () => {
+ await openGlDropdown();
await axios.waitForAll();
- wrapper.find('.js-ci-action').trigger('click');
+ findCiActionBtn().trigger('click');
await axios.waitForAll();
+ };
+
+ describe('within pipeline table', () => {
+ beforeEach(() => {
+ createComponent({ type: 'PIPELINES_TABLE' });
+ });
+
+ it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
+ await clickCiAction();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ });
+ });
+
+ describe('in MR widget', () => {
+ beforeEach(() => {
+ jest.spyOn($.fn, 'dropdown');
+ createComponent();
+ });
+
+ it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
+ const hidden = jest.fn();
+
+ wrapper.vm.$root.$on('bv::dropdown::hide', hidden);
+
+ expect(hidden).toHaveBeenCalledTimes(0);
+
+ await clickCiAction();
- expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ expect(hidden).toHaveBeenCalledTimes(1);
+ });
});
});
});
diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
index f7ff36c0a46..6258b08dfbb 100644
--- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
@@ -2,10 +2,10 @@ import MockAdapter from 'axios-mock-adapter';
import { getJSONFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import * as actions from '~/pipelines/stores/test_reports/actions';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash.js');
@@ -16,7 +16,7 @@ describe('Actions TestReports Store', () => {
const testReports = getJSONFixture('pipelines/test_report.json');
const summary = { total_count: 1 };
- const suiteEndpoint = `${TEST_HOST}/tests/:suite_name.json`;
+ const suiteEndpoint = `${TEST_HOST}/tests/suite.json`;
const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`;
const defaultState = {
suiteEndpoint,
@@ -69,9 +69,8 @@ describe('Actions TestReports Store', () => {
beforeEach(() => {
const buildIds = [1];
testReports.test_suites[0].build_ids = buildIds;
- const endpoint = suiteEndpoint.replace(':suite_name', testReports.test_suites[0].name);
mock
- .onGet(endpoint, { params: { build_ids: buildIds } })
+ .onGet(suiteEndpoint, { params: { build_ids: buildIds } })
.replyOnce(200, testReports.test_suites[0], {});
});
diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
index 7382a6beefa..f8298fdaba5 100644
--- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
@@ -1,6 +1,10 @@
import { getJSONFixture } from 'helpers/fixtures';
import * as getters from '~/pipelines/stores/test_reports/getters';
-import { iconForTestStatus, formattedTime } from '~/pipelines/stores/test_reports/utils';
+import {
+ iconForTestStatus,
+ formatFilePath,
+ formattedTime,
+} from '~/pipelines/stores/test_reports/utils';
describe('Getters TestReports Store', () => {
let state;
@@ -8,6 +12,7 @@ describe('Getters TestReports Store', () => {
const testReports = getJSONFixture('pipelines/test_report.json');
const defaultState = {
+ blobPath: '/test/blob/path',
testReports,
selectedSuiteIndex: 0,
pageInfo: {
@@ -17,6 +22,7 @@ describe('Getters TestReports Store', () => {
};
const emptyState = {
+ blobPath: '',
testReports: {},
selectedSuite: null,
pageInfo: {
@@ -74,6 +80,7 @@ describe('Getters TestReports Store', () => {
const expected = testReports.test_suites[0].test_cases
.map((x) => ({
...x,
+ filePath: `${state.blobPath}/${formatFilePath(x.file)}`,
formattedTime: formattedTime(x.execution_time),
icon: iconForTestStatus(x.status),
}))
@@ -87,6 +94,70 @@ describe('Getters TestReports Store', () => {
expect(getters.getSuiteTests(state)).toEqual([]);
});
+
+ describe('when a test case classname property is null', () => {
+ it('should return an empty string value for the classname property', () => {
+ const testCases = testReports.test_suites[0].test_cases;
+ setupState({
+ ...defaultState,
+ testReports: {
+ ...testReports,
+ test_suites: [
+ {
+ test_cases: testCases.map((testCase) => ({
+ ...testCase,
+ classname: null,
+ })),
+ },
+ ],
+ },
+ });
+
+ const expected = testCases
+ .map((x) => ({
+ ...x,
+ classname: '',
+ filePath: `${state.blobPath}/${formatFilePath(x.file)}`,
+ formattedTime: formattedTime(x.execution_time),
+ icon: iconForTestStatus(x.status),
+ }))
+ .slice(0, state.pageInfo.perPage);
+
+ expect(getters.getSuiteTests(state)).toEqual(expected);
+ });
+ });
+
+ describe('when a test case name property is null', () => {
+ it('should return an empty string value for the name property', () => {
+ const testCases = testReports.test_suites[0].test_cases;
+ setupState({
+ ...defaultState,
+ testReports: {
+ ...testReports,
+ test_suites: [
+ {
+ test_cases: testCases.map((testCase) => ({
+ ...testCase,
+ name: null,
+ })),
+ },
+ ],
+ },
+ });
+
+ const expected = testCases
+ .map((x) => ({
+ ...x,
+ name: '',
+ filePath: `${state.blobPath}/${formatFilePath(x.file)}`,
+ formattedTime: formattedTime(x.execution_time),
+ icon: iconForTestStatus(x.status),
+ }))
+ .slice(0, state.pageInfo.perPage);
+
+ expect(getters.getSuiteTests(state)).toEqual(expected);
+ });
+ });
});
describe('getSuiteTestCount', () => {
diff --git a/spec/frontend/pipelines/test_reports/stores/utils_spec.js b/spec/frontend/pipelines/test_reports/stores/utils_spec.js
index 7e632d099fc..703fe69026c 100644
--- a/spec/frontend/pipelines/test_reports/stores/utils_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/utils_spec.js
@@ -1,6 +1,20 @@
-import { formattedTime } from '~/pipelines/stores/test_reports/utils';
+import { formatFilePath, formattedTime } from '~/pipelines/stores/test_reports/utils';
describe('Test reports utils', () => {
+ describe('formatFilePath', () => {
+ it.each`
+ file | expected
+ ${'./test.js'} | ${'test.js'}
+ ${'/test.js'} | ${'test.js'}
+ ${'.//////////////test.js'} | ${'test.js'}
+ ${'test.js'} | ${'test.js'}
+ ${'mock/path./test.js'} | ${'mock/path./test.js'}
+ ${'./mock/path./test.js'} | ${'mock/path./test.js'}
+ `('should format $file to be $expected', ({ file, expected }) => {
+ expect(formatFilePath(file)).toBe(expected);
+ });
+ });
+
describe('formattedTime', () => {
describe('when time is smaller than a second', () => {
it('should return time in milliseconds fixed to 2 decimals', () => {
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 bfb8b43778d..e866586a2c3 100644
--- a/spec/frontend/pipelines/test_reports/test_case_details_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
@@ -11,12 +11,17 @@ describe('Test case details', () => {
classname: 'spec.test_spec',
name: 'Test#something cool',
formattedTime: '10.04ms',
+ recent_failures: {
+ count: 2,
+ base_branch: 'master',
+ },
system_output: 'Line 42 is broken',
};
const findModal = () => wrapper.find(GlModal);
const findName = () => wrapper.find('[data-testid="test-case-name"]');
const findDuration = () => wrapper.find('[data-testid="test-case-duration"]');
+ const findRecentFailures = () => wrapper.find('[data-testid="test-case-recent-failures"]');
const findSystemOutput = () => wrapper.find('[data-testid="test-case-trace"]');
const createComponent = (testCase = {}) => {
@@ -56,6 +61,36 @@ describe('Test case details', () => {
});
});
+ describe('when test case has recent failures', () => {
+ describe('has only 1 recent failure', () => {
+ it('renders the recent failure', () => {
+ createComponent({ recent_failures: { ...defaultTestCase.recent_failures, count: 1 } });
+
+ expect(findRecentFailures().text()).toContain(
+ `Failed 1 time in ${defaultTestCase.recent_failures.base_branch} in the last 14 days`,
+ );
+ });
+ });
+
+ describe('has more than 1 recent failure', () => {
+ it('renders the recent failures', () => {
+ createComponent();
+
+ expect(findRecentFailures().text()).toContain(
+ `Failed ${defaultTestCase.recent_failures.count} times in ${defaultTestCase.recent_failures.base_branch} in the last 14 days`,
+ );
+ });
+ });
+ });
+
+ describe('when test case does not have recent failures', () => {
+ it('does not render the recent failures', () => {
+ createComponent({ recent_failures: null });
+
+ expect(findRecentFailures().exists()).toBe(false);
+ });
+ });
+
describe('when test case has system output', () => {
it('renders the test case system output', () => {
createComponent();
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index c8ab18b9086..da5763ddf8e 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
import TestSummary from '~/pipelines/components/test_reports/test_summary.vue';
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 b8fd056610b..a87145cc557 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,10 +1,11 @@
-import Vuex from 'vuex';
+import { GlButton, GlFriendlyWrap, GlLink, GlPagination } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
-import { GlButton, GlFriendlyWrap, GlPagination } from '@gitlab/ui';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
-import * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants';
+import * as getters from '~/pipelines/stores/test_reports/getters';
+import { formatFilePath } from '~/pipelines/stores/test_reports/utils';
import skippedTestCases from './mock_data';
const localVue = createLocalVue();
@@ -20,15 +21,18 @@ describe('Test reports suite table', () => {
testSuite.test_cases = [...testSuite.test_cases, ...skippedTestCases];
const testCases = testSuite.test_cases;
+ const blobPath = '/test/blob/path';
const noCasesMessage = () => wrapper.find('.js-no-test-cases');
const allCaseRows = () => wrapper.findAll('.js-case-row');
const findCaseRowAtIndex = (index) => wrapper.findAll('.js-case-row').at(index);
+ const findLinkForRow = (row) => row.find(GlLink);
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
const createComponent = (suite = testSuite, perPage = 20) => {
store = new Vuex.Store({
state: {
+ blobPath,
testReports: {
test_suites: [suite],
},
@@ -64,7 +68,7 @@ describe('Test reports suite table', () => {
beforeEach(() => createComponent());
it('renders the correct number of rows', () => {
- expect(allCaseRows().length).toBe(testCases.length);
+ expect(allCaseRows()).toHaveLength(testCases.length);
});
it.each([
@@ -82,9 +86,13 @@ describe('Test reports suite table', () => {
it('renders the file name for the test with a copy button', () => {
const { file } = testCases[0];
+ const relativeFile = formatFilePath(file);
+ const filePath = `${blobPath}/${relativeFile}`;
const row = findCaseRowAtIndex(0);
+ const fileLink = findLinkForRow(row);
const button = row.find(GlButton);
+ expect(fileLink.attributes('href')).toBe(filePath);
expect(row.text()).toContain(file);
expect(button.exists()).toBe(true);
expect(button.attributes('data-clipboard-text')).toBe(file);
@@ -106,4 +114,32 @@ describe('Test reports suite table', () => {
expect(wrapper.find(GlPagination).exists()).toBe(true);
});
});
+
+ describe('when a test case classname property is null', () => {
+ it('still renders all test cases', () => {
+ createComponent({
+ ...testSuite,
+ test_cases: testSuite.test_cases.map((testCase) => ({
+ ...testCase,
+ classname: null,
+ })),
+ });
+
+ expect(allCaseRows()).toHaveLength(testCases.length);
+ });
+ });
+
+ describe('when a test case name property is null', () => {
+ it('still renders all test cases', () => {
+ createComponent({
+ ...testSuite,
+ test_cases: testSuite.test_cases.map((testCase) => ({
+ ...testCase,
+ name: null,
+ })),
+ });
+
+ expect(allCaseRows()).toHaveLength(testCases.length);
+ });
+ });
});
diff --git a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
index b585536ae09..892a3742fea 100644
--- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index b7bc8d08a0f..55a19ef5165 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
describe('Timeago component', () => {
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 371ba5a4f9b..7ddbbb3b005 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -1,6 +1,6 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
-import { stubComponent } from 'helpers/stub_component';
import { shallowMount } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
import Api from '~/api';
import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
import { users } from '../mock_data';
diff --git a/spec/frontend/popovers/components/popovers_spec.js b/spec/frontend/popovers/components/popovers_spec.js
index 63e0b3d9c49..0c164d97564 100644
--- a/spec/frontend/popovers/components/popovers_spec.js
+++ b/spec/frontend/popovers/components/popovers_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlPopover } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Popovers from '~/popovers/components/popovers.vue';
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 63e27473979..f1784500baf 100644
--- a/spec/frontend/profile/account/components/delete_account_modal_spec.js
+++ b/spec/frontend/profile/account/components/delete_account_modal_spec.js
@@ -1,8 +1,8 @@
+import { mount } from '@vue/test-utils';
+import { merge } from 'lodash';
import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
-import { merge } from 'lodash';
-import { mount } from '@vue/test-utils';
import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue';
const GlModalStub = {
diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js
index 91c3c81ab30..8295d1d43cf 100644
--- a/spec/frontend/profile/account/components/update_username_spec.js
+++ b/spec/frontend/profile/account/components/update_username_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap b/spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap
deleted file mode 100644
index 2fd1fd6a04e..00000000000
--- a/spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap
+++ /dev/null
@@ -1,67 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`IntegrationView component should render IntegrationView properly 1`] = `
-<div
- name="sourcegraph"
->
- <label
- class="label-bold"
- >
-
- Foo
-
- </label>
-
- <gl-link-stub
- class="has-tooltip"
- href="http://foo.com/help"
- title="More information"
- >
- <gl-icon-stub
- class="vertical-align-middle"
- name="question-o"
- size="16"
- />
- </gl-link-stub>
-
- <div
- class="form-group form-check"
- data-testid="profile-preferences-integration-form-group"
- >
- <input
- data-testid="profile-preferences-integration-hidden-field"
- name="user[foo_enabled]"
- type="hidden"
- value="0"
- />
-
- <input
- class="form-check-input"
- data-testid="profile-preferences-integration-checkbox"
- id="user_foo_enabled"
- name="user[foo_enabled]"
- type="checkbox"
- value="1"
- />
-
- <label
- class="form-check-label"
- for="user_foo_enabled"
- >
-
- Enable foo
-
- </label>
-
- <gl-form-text-stub
- tag="div"
- textvariant="muted"
- >
- <integration-help-text-stub
- message="Click %{linkStart}Foo%{linkEnd}!"
- messageurl="http://foo.com"
- />
- </gl-form-text-stub>
- </div>
-</div>
-`;
diff --git a/spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap b/spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap
deleted file mode 100644
index 4df92cf86a5..00000000000
--- a/spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap
+++ /dev/null
@@ -1,51 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ProfilePreferences component should render ProfilePreferences properly 1`] = `
-<div
- class="row gl-mt-3 js-preferences-form"
->
- <div
- class="col-sm-12"
- >
- <hr
- data-testid="profile-preferences-integrations-rule"
- />
- </div>
-
- <div
- class="col-lg-4 profile-settings-sidebar"
- >
- <h4
- class="gl-mt-0"
- data-testid="profile-preferences-integrations-heading"
- >
-
- Integrations
-
- </h4>
-
- <p>
-
- Customize integrations with third party services.
-
- </p>
- </div>
-
- <div
- class="col-lg-8"
- >
- <integration-view-stub
- config="[object Object]"
- helplink="http://foo.com/help"
- message="Click %{linkStart}Foo%{linkEnd}!"
- messageurl="http://foo.com"
- />
- <integration-view-stub
- config="[object Object]"
- helplink="http://bar.com/help"
- message="Click %{linkStart}Bar%{linkEnd}!"
- messageurl="http://bar.com"
- />
- </div>
-</div>
-`;
diff --git a/spec/frontend/profile/preferences/components/integration_view_spec.js b/spec/frontend/profile/preferences/components/integration_view_spec.js
index 5d55a089119..6ab0c70298c 100644
--- a/spec/frontend/profile/preferences/components/integration_view_spec.js
+++ b/spec/frontend/profile/preferences/components/integration_view_spec.js
@@ -1,9 +1,9 @@
+import { GlFormText } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlFormText } from '@gitlab/ui';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IntegrationView from '~/profile/preferences/components/integration_view.vue';
import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { integrationViews, userFields } from '../mock_data';
const viewProps = convertObjectPropsToCamelCase(integrationViews[0]);
@@ -115,10 +115,4 @@ describe('IntegrationView component', () => {
expect(findFormGroupLabel().text()).toBe('Enable foo');
});
-
- it('should render IntegrationView properly', () => {
- wrapper = createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
});
diff --git a/spec/frontend/profile/preferences/components/profile_preferences_spec.js b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
index fcc27d8faaf..82c41178410 100644
--- a/spec/frontend/profile/preferences/components/profile_preferences_spec.js
+++ b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
@@ -1,27 +1,58 @@
+import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-
-import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IntegrationView from '~/profile/preferences/components/integration_view.vue';
-import { integrationViews, userFields } from '../mock_data';
+import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue';
+import { i18n } from '~/profile/preferences/constants';
+import { integrationViews, userFields, bodyClasses } from '../mock_data';
+
+const expectedUrl = '/foo';
describe('ProfilePreferences component', () => {
let wrapper;
const defaultProvide = {
integrationViews: [],
userFields,
+ bodyClasses,
+ themes: [{ id: 1, css_class: 'foo' }],
+ profilePreferencesPath: '/update-profile',
+ formEl: document.createElement('form'),
};
function createComponent(options = {}) {
- const { props = {}, provide = {} } = options;
- return shallowMount(ProfilePreferences, {
- provide: {
- ...defaultProvide,
- ...provide,
- },
- propsData: props,
- });
+ const { props = {}, provide = {}, attachTo } = options;
+ return extendedWrapper(
+ shallowMount(ProfilePreferences, {
+ provide: {
+ ...defaultProvide,
+ ...provide,
+ },
+ propsData: props,
+ attachTo,
+ }),
+ );
+ }
+
+ function findIntegrationsDivider() {
+ return wrapper.findByTestId('profile-preferences-integrations-rule');
+ }
+
+ function findIntegrationsHeading() {
+ return wrapper.findByTestId('profile-preferences-integrations-heading');
+ }
+
+ function findSubmitButton() {
+ return wrapper.findComponent(GlButton);
}
+ function findFlashError() {
+ return document.querySelector('.flash-container .flash-text');
+ }
+
+ beforeEach(() => {
+ setFixtures('<div class="flash-container"></div>');
+ });
+
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -30,8 +61,8 @@ describe('ProfilePreferences component', () => {
it('should not render Integrations section', () => {
wrapper = createComponent();
const views = wrapper.findAll(IntegrationView);
- const divider = wrapper.find('[data-testid="profile-preferences-integrations-rule"]');
- const heading = wrapper.find('[data-testid="profile-preferences-integrations-heading"]');
+ const divider = findIntegrationsDivider();
+ const heading = findIntegrationsHeading();
expect(divider.exists()).toBe(false);
expect(heading.exists()).toBe(false);
@@ -40,8 +71,8 @@ describe('ProfilePreferences component', () => {
it('should render Integration section', () => {
wrapper = createComponent({ provide: { integrationViews } });
- const divider = wrapper.find('[data-testid="profile-preferences-integrations-rule"]');
- const heading = wrapper.find('[data-testid="profile-preferences-integrations-heading"]');
+ const divider = findIntegrationsDivider();
+ const heading = findIntegrationsHeading();
const views = wrapper.findAll(IntegrationView);
expect(divider.exists()).toBe(true);
@@ -49,9 +80,84 @@ describe('ProfilePreferences component', () => {
expect(views).toHaveLength(integrationViews.length);
});
- it('should render ProfilePreferences properly', () => {
- wrapper = createComponent({ provide: { integrationViews } });
+ describe('form submit', () => {
+ let form;
- expect(wrapper.element).toMatchSnapshot();
+ beforeEach(() => {
+ const div = document.createElement('div');
+ div.classList.add('container-fluid');
+ document.body.appendChild(div);
+ document.body.classList.add('content-wrapper');
+
+ form = document.createElement('form');
+ form.setAttribute('url', expectedUrl);
+ form.setAttribute('method', 'put');
+
+ const input = document.createElement('input');
+ input.setAttribute('name', 'user[theme_id]');
+ input.setAttribute('type', 'radio');
+ input.setAttribute('value', '1');
+ input.setAttribute('checked', 'checked');
+ form.appendChild(input);
+
+ wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body });
+
+ const beforeSendEvent = new CustomEvent('ajax:beforeSend');
+ form.dispatchEvent(beforeSendEvent);
+ });
+
+ it('disables the submit button', async () => {
+ await wrapper.vm.$nextTick();
+ const button = findSubmitButton();
+ expect(button.props('disabled')).toBe(true);
+ });
+
+ it('success re-enables the submit button', async () => {
+ const successEvent = new CustomEvent('ajax:success');
+ form.dispatchEvent(successEvent);
+
+ await wrapper.vm.$nextTick();
+ const button = findSubmitButton();
+ expect(button.props('disabled')).toBe(false);
+ });
+
+ it('error re-enables the submit button', async () => {
+ const errorEvent = new CustomEvent('ajax:error');
+ form.dispatchEvent(errorEvent);
+
+ await wrapper.vm.$nextTick();
+ const button = findSubmitButton();
+ expect(button.props('disabled')).toBe(false);
+ });
+
+ it('displays the default success message', () => {
+ const successEvent = new CustomEvent('ajax:success');
+ form.dispatchEvent(successEvent);
+
+ expect(findFlashError().innerText.trim()).toEqual(i18n.defaultSuccess);
+ });
+
+ it('displays the custom success message', () => {
+ const message = 'foo';
+ const successEvent = new CustomEvent('ajax:success', { detail: [{ message }] });
+ form.dispatchEvent(successEvent);
+
+ expect(findFlashError().innerText.trim()).toEqual(message);
+ });
+
+ it('displays the default error message', () => {
+ const errorEvent = new CustomEvent('ajax:error');
+ form.dispatchEvent(errorEvent);
+
+ expect(findFlashError().innerText.trim()).toEqual(i18n.defaultError);
+ });
+
+ it('displays the custom error message', () => {
+ const message = 'bar';
+ const errorEvent = new CustomEvent('ajax:error', { detail: [{ message }] });
+ form.dispatchEvent(errorEvent);
+
+ expect(findFlashError().innerText.trim()).toEqual(message);
+ });
});
});
diff --git a/spec/frontend/profile/preferences/mock_data.js b/spec/frontend/profile/preferences/mock_data.js
index d07d5f565dc..ce33fc79a39 100644
--- a/spec/frontend/profile/preferences/mock_data.js
+++ b/spec/frontend/profile/preferences/mock_data.js
@@ -16,3 +16,5 @@ export const integrationViews = [
export const userFields = {
foo_enabled: true,
};
+
+export const bodyClasses = 'ui-light-indigo ui-light gl-dark';
diff --git a/spec/frontend/project_find_file_spec.js b/spec/frontend/project_find_file_spec.js
index 1af97dbca0a..5919910d791 100644
--- a/spec/frontend/project_find_file_spec.js
+++ b/spec/frontend/project_find_file_spec.js
@@ -2,8 +2,8 @@ import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
import { sanitize } from '~/lib/dompurify';
-import ProjectFindFile from '~/project_find_file';
import axios from '~/lib/utils/axios_utils';
+import ProjectFindFile from '~/project_find_file';
jest.mock('~/lib/dompurify', () => ({
addHook: jest.fn(),
diff --git a/spec/frontend/projects/commit/components/branches_dropdown_spec.js b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
index 9fa7d658405..7686c28c7fc 100644
--- a/spec/frontend/projects/commit/components/branches_dropdown_spec.js
+++ b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
@@ -1,8 +1,8 @@
+import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue';
Vue.use(Vuex);
diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js
index 1c37b82fed3..1569f5b4bbe 100644
--- a/spec/frontend/projects/commit/components/form_modal_spec.js
+++ b/spec/frontend/projects/commit/components/form_modal_spec.js
@@ -1,12 +1,13 @@
-import MockAdapter from 'axios-mock-adapter';
-import { shallowMount, mount, createWrapper } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { GlModal, GlForm, GlFormCheckbox, GlSprintf } from '@gitlab/ui';
import { within } from '@testing-library/dom';
+import { shallowMount, mount, createWrapper } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import axios from '~/lib/utils/axios_utils';
-import eventHub from '~/projects/commit/event_hub';
-import CommitFormModal from '~/projects/commit/components/form_modal.vue';
+import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue';
+import CommitFormModal from '~/projects/commit/components/form_modal.vue';
+import eventHub from '~/projects/commit/event_hub';
import createStore from '~/projects/commit/store';
import mockData from '../mock_data';
@@ -64,7 +65,7 @@ describe('CommitFormModal', () => {
wrapper.vm.show();
- expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', mockData.modalPropsData.modalId);
+ expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, mockData.modalPropsData.modalId);
});
it('Clears the modal state once modal is hidden', () => {
diff --git a/spec/frontend/projects/commit/components/form_trigger_spec.js b/spec/frontend/projects/commit/components/form_trigger_spec.js
index ca51419d6a5..4503493c0a6 100644
--- a/spec/frontend/projects/commit/components/form_trigger_spec.js
+++ b/spec/frontend/projects/commit/components/form_trigger_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import FormTrigger from '~/projects/commit/components/form_trigger.vue';
import eventHub from '~/projects/commit/event_hub';
diff --git a/spec/frontend/projects/commit/store/actions_spec.js b/spec/frontend/projects/commit/store/actions_spec.js
index ec528d4ee88..458372229cf 100644
--- a/spec/frontend/projects/commit/store/actions_spec.js
+++ b/spec/frontend/projects/commit/store/actions_spec.js
@@ -1,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
-import getInitialState from '~/projects/commit/store/state';
+import axios from '~/lib/utils/axios_utils';
+import { PROJECT_BRANCHES_ERROR } from '~/projects/commit/constants';
import * as actions from '~/projects/commit/store/actions';
import * as types from '~/projects/commit/store/mutation_types';
+import getInitialState from '~/projects/commit/store/state';
import mockData from '../mock_data';
-import { PROJECT_BRANCHES_ERROR } from '~/projects/commit/constants';
jest.mock('~/flash.js');
diff --git a/spec/frontend/projects/commit/store/mutations_spec.js b/spec/frontend/projects/commit/store/mutations_spec.js
index 59ab3d9a74a..2ea50e71772 100644
--- a/spec/frontend/projects/commit/store/mutations_spec.js
+++ b/spec/frontend/projects/commit/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/projects/commit/store/mutations';
import * as types from '~/projects/commit/store/mutation_types';
+import mutations from '~/projects/commit/store/mutations';
describe('Commit form modal mutations', () => {
let stateCopy;
diff --git a/spec/frontend/projects/commit_box/info/load_branches_spec.js b/spec/frontend/projects/commit_box/info/load_branches_spec.js
index ebd4ee45dab..8100200cbdd 100644
--- a/spec/frontend/projects/commit_box/info/load_branches_spec.js
+++ b/spec/frontend/projects/commit_box/info/load_branches_spec.js
@@ -1,6 +1,6 @@
import axios from 'axios';
-import waitForPromises from 'helpers/wait_for_promises';
import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
import { loadBranches } from '~/projects/commit_box/info/load_branches';
const mockCommitPath = '/commit/abcd/branches';
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index 63920ddfd72..9a8f7ff7582 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -1,6 +1,6 @@
+import { GlDropdown, GlDropdownSectionHeader, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlDropdown, GlDropdownSectionHeader, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js
index a842aaa2a76..e2c993b8395 100644
--- a/spec/frontend/projects/commits/store/actions_spec.js
+++ b/spec/frontend/projects/commits/store/actions_spec.js
@@ -1,10 +1,10 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import * as types from '~/projects/commits/store/mutation_types';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import actions from '~/projects/commits/store/actions';
+import * as types from '~/projects/commits/store/mutation_types';
import createState from '~/projects/commits/store/state';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
diff --git a/spec/frontend/projects/compare/components/app_spec.js b/spec/frontend/projects/compare/components/app_spec.js
new file mode 100644
index 00000000000..d28a30e93b1
--- /dev/null
+++ b/spec/frontend/projects/compare/components/app_spec.js
@@ -0,0 +1,116 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import CompareApp from '~/projects/compare/components/app.vue';
+import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
+
+jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
+
+const projectCompareIndexPath = 'some/path';
+const refsProjectPath = 'some/refs/path';
+const paramsFrom = 'master';
+const paramsTo = 'master';
+
+describe('CompareApp component', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(CompareApp, {
+ propsData: {
+ projectCompareIndexPath,
+ refsProjectPath,
+ paramsFrom,
+ paramsTo,
+ projectMergeRequestPath: '',
+ createMrPath: '',
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders component with prop', () => {
+ expect(wrapper.props()).toEqual(
+ expect.objectContaining({
+ projectCompareIndexPath,
+ refsProjectPath,
+ paramsFrom,
+ paramsTo,
+ }),
+ );
+ });
+
+ it('contains the correct form attributes', () => {
+ expect(wrapper.attributes('action')).toBe(projectCompareIndexPath);
+ expect(wrapper.attributes('method')).toBe('POST');
+ });
+
+ it('has input with csrf token', () => {
+ expect(wrapper.find('input[name="authenticity_token"]').attributes('value')).toBe(
+ 'mock-csrf-token',
+ );
+ });
+
+ it('has ellipsis', () => {
+ expect(wrapper.find('[data-testid="ellipsis"]').exists()).toBe(true);
+ });
+
+ it('render Source and Target BranchDropdown components', () => {
+ const branchDropdowns = wrapper.findAll(RevisionDropdown);
+
+ expect(branchDropdowns.length).toBe(2);
+ expect(branchDropdowns.at(0).props('revisionText')).toBe('Source');
+ expect(branchDropdowns.at(1).props('revisionText')).toBe('Target');
+ });
+
+ describe('compare button', () => {
+ const findCompareButton = () => wrapper.find(GlButton);
+
+ it('renders button', () => {
+ expect(findCompareButton().exists()).toBe(true);
+ });
+
+ it('submits form', () => {
+ findCompareButton().vm.$emit('click');
+ expect(wrapper.find('form').element.submit).toHaveBeenCalled();
+ });
+
+ it('has compare text', () => {
+ expect(findCompareButton().text()).toBe('Compare');
+ });
+ });
+
+ describe('merge request buttons', () => {
+ const findProjectMrButton = () => wrapper.find('[data-testid="projectMrButton"]');
+ const findCreateMrButton = () => wrapper.find('[data-testid="createMrButton"]');
+
+ it('does not have merge request buttons', () => {
+ createComponent();
+ expect(findProjectMrButton().exists()).toBe(false);
+ expect(findCreateMrButton().exists()).toBe(false);
+ });
+
+ it('has "View open merge request" button', () => {
+ createComponent({
+ projectMergeRequestPath: 'some/project/merge/request/path',
+ });
+ expect(findProjectMrButton().exists()).toBe(true);
+ expect(findCreateMrButton().exists()).toBe(false);
+ });
+
+ it('has "Create merge request" button', () => {
+ createComponent({
+ createMrPath: 'some/create/create/mr/path',
+ });
+ expect(findProjectMrButton().exists()).toBe(false);
+ expect(findCreateMrButton().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
new file mode 100644
index 00000000000..f3ff5e26d2b
--- /dev/null
+++ b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
@@ -0,0 +1,92 @@
+import { GlDropdown } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
+
+const defaultProps = {
+ refsProjectPath: 'some/refs/path',
+ revisionText: 'Target',
+ paramsName: 'from',
+ paramsBranch: 'master',
+};
+
+jest.mock('~/flash');
+
+describe('RevisionDropdown component', () => {
+ let wrapper;
+ let axiosMock;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(RevisionDropdown, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ axiosMock.restore();
+ });
+
+ const findGlDropdown = () => wrapper.find(GlDropdown);
+
+ it('sets hidden input', () => {
+ createComponent();
+ expect(wrapper.find('input[type="hidden"]').attributes('value')).toBe(
+ defaultProps.paramsBranch,
+ );
+ });
+
+ it('update the branches on success', async () => {
+ const Branches = ['branch-1', 'branch-2'];
+ const Tags = ['tag-1', 'tag-2', 'tag-3'];
+
+ axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(200, {
+ Branches,
+ Tags,
+ });
+
+ createComponent();
+
+ await axios.waitForAll();
+
+ expect(wrapper.vm.branches).toEqual(Branches);
+ expect(wrapper.vm.tags).toEqual(Tags);
+ });
+
+ it('shows flash message on error', async () => {
+ axiosMock.onGet('some/invalid/path').replyOnce(404);
+
+ createComponent();
+
+ await wrapper.vm.fetchBranchesAndTags();
+ expect(createFlash).toHaveBeenCalled();
+ });
+
+ describe('GlDropdown component', () => {
+ it('renders props', () => {
+ createComponent();
+ expect(wrapper.props()).toEqual(expect.objectContaining(defaultProps));
+ });
+
+ it('display default text', () => {
+ createComponent({
+ paramsBranch: null,
+ });
+ expect(findGlDropdown().props('text')).toBe('Select branch/tag');
+ });
+
+ it('display params branch text', () => {
+ createComponent();
+ expect(findGlDropdown().props('text')).toBe(defaultProps.paramsBranch);
+ });
+ });
+});
diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
index 0b9f095a700..f0d72124379 100644
--- a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
+++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
@@ -53,12 +53,12 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
variant="danger"
>
<gl-sprintf-stub
- message="Once a project is permanently deleted it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd} including issues, merge requests etc."
+ message="Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc."
/>
</gl-alert-stub>
<p>
- This action cannot be undone. You will lose this project's repository and all content: issues, merge requests, etc.
+ This action cannot be undone. You will lose this project's repository and all related resources, including issues, merge requests, etc.
</p>
<p
diff --git a/spec/frontend/projects/components/shared/delete_button_spec.js b/spec/frontend/projects/components/shared/delete_button_spec.js
index cf7e41a2df2..3e491584670 100644
--- a/spec/frontend/projects/components/shared/delete_button_spec.js
+++ b/spec/frontend/projects/components/shared/delete_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
diff --git a/spec/frontend/projects/experiment_new_project_creation/components/app_spec.js b/spec/frontend/projects/experiment_new_project_creation/components/app_spec.js
index 9a5f200f5a9..b4ae50341d4 100644
--- a/spec/frontend/projects/experiment_new_project_creation/components/app_spec.js
+++ b/spec/frontend/projects/experiment_new_project_creation/components/app_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlBreadcrumb } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import App from '~/projects/experiment_new_project_creation/components/app.vue';
-import WelcomePage from '~/projects/experiment_new_project_creation/components/welcome.vue';
import LegacyContainer from '~/projects/experiment_new_project_creation/components/legacy_container.vue';
+import WelcomePage from '~/projects/experiment_new_project_creation/components/welcome.vue';
describe('Experimental new project creation app', () => {
let wrapper;
diff --git a/spec/frontend/projects/members/utils_spec.js b/spec/frontend/projects/members/utils_spec.js
new file mode 100644
index 00000000000..813e8455e85
--- /dev/null
+++ b/spec/frontend/projects/members/utils_spec.js
@@ -0,0 +1,14 @@
+import { projectMemberRequestFormatter } from '~/projects/members/utils';
+
+describe('project member utils', () => {
+ describe('projectMemberRequestFormatter', () => {
+ it('returns expected format', () => {
+ expect(
+ projectMemberRequestFormatter({
+ accessLevel: 50,
+ expires_at: '2020-10-16',
+ }),
+ ).toEqual({ project_member: { access_level: 50, expires_at: '2020-10-16' } });
+ });
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
index 44329944097..e8aace14db4 100644
--- a/spec/frontend/projects/pipelines/charts/components/app_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -1,32 +1,19 @@
-import { merge } from 'lodash';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import VueApollo from 'vue-apollo';
import { GlTabs, GlTab } from '@gitlab/ui';
-import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMount } from '@vue/test-utils';
+import { merge } from 'lodash';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'helpers/test_constants';
+import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
import Component from '~/projects/pipelines/charts/components/app.vue';
import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue';
-import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql';
-import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql';
-import { mockPipelineCount, mockPipelineStatistics } from '../mock_data';
-const projectPath = 'gitlab-org/gitlab';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+jest.mock('~/lib/utils/url_utility');
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
describe('ProjectsPipelinesChartsApp', () => {
let wrapper;
- function createMockApolloProvider() {
- const requestHandlers = [
- [getPipelineCountByStatus, jest.fn().mockResolvedValue(mockPipelineCount)],
- [getProjectPipelineStatistics, jest.fn().mockResolvedValue(mockPipelineStatistics)],
- ];
-
- return createMockApollo(requestHandlers);
- }
-
function createComponent(mountOptions = {}) {
wrapper = shallowMount(
Component,
@@ -34,11 +21,8 @@ describe('ProjectsPipelinesChartsApp', () => {
{},
{
provide: {
- projectPath,
shouldRenderDeploymentFrequencyCharts: false,
},
- localVue,
- apolloProvider: createMockApolloProvider(),
stubs: {
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
},
@@ -57,52 +41,15 @@ describe('ProjectsPipelinesChartsApp', () => {
wrapper = null;
});
- describe('pipelines charts', () => {
- it('displays the pipeline charts', () => {
- const chart = wrapper.find(PipelineCharts);
- const analytics = mockPipelineStatistics.data.project.pipelineAnalytics;
-
- const {
- totalPipelines: total,
- successfulPipelines: success,
- failedPipelines: failed,
- } = mockPipelineCount.data.project;
-
- expect(chart.exists()).toBe(true);
- expect(chart.props()).toMatchObject({
- counts: {
- failed: failed.count,
- success: success.count,
- total: total.count,
- successRatio: (success.count / (success.count + failed.count)) * 100,
- },
- lastWeek: {
- labels: analytics.weekPipelinesLabels,
- totals: analytics.weekPipelinesTotals,
- success: analytics.weekPipelinesSuccessful,
- },
- lastMonth: {
- labels: analytics.monthPipelinesLabels,
- totals: analytics.monthPipelinesTotals,
- success: analytics.monthPipelinesSuccessful,
- },
- lastYear: {
- labels: analytics.yearPipelinesLabels,
- totals: analytics.yearPipelinesTotals,
- success: analytics.yearPipelinesSuccessful,
- },
- timesChart: {
- labels: analytics.pipelineTimesLabels,
- values: analytics.pipelineTimesValues,
- },
- });
- });
- });
-
- const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
const findGlTabs = () => wrapper.find(GlTabs);
const findAllGlTab = () => wrapper.findAll(GlTab);
const findGlTabAt = (i) => findAllGlTab().at(i);
+ const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
+ const findPipelineCharts = () => wrapper.find(PipelineCharts);
+
+ it('renders the pipeline charts', () => {
+ expect(findPipelineCharts().exists()).toBe(true);
+ });
describe('when shouldRenderDeploymentFrequencyCharts is true', () => {
beforeEach(() => {
@@ -115,6 +62,97 @@ describe('ProjectsPipelinesChartsApp', () => {
expect(findGlTabAt(1).attributes('title')).toBe('Deployments');
expect(findDeploymentFrequencyCharts().exists()).toBe(true);
});
+
+ it('sets the tab and url when a tab is clicked', async () => {
+ let chartsPath;
+ setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
+
+ mergeUrlParams.mockImplementation(({ chart }, path) => {
+ expect(chart).toBe('deployments');
+ expect(path).toBe(window.location.pathname);
+ chartsPath = `${path}?chart=${chart}`;
+ return chartsPath;
+ });
+
+ updateHistory.mockImplementation(({ url }) => {
+ expect(url).toBe(chartsPath);
+ });
+ const tabs = findGlTabs();
+
+ expect(tabs.attributes('value')).toBe('0');
+
+ tabs.vm.$emit('input', 1);
+
+ await wrapper.vm.$nextTick();
+
+ expect(tabs.attributes('value')).toBe('1');
+ });
+
+ it('should not try to push history if the tab does not change', async () => {
+ setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
+
+ mergeUrlParams.mockImplementation(({ chart }, path) => `${path}?chart=${chart}`);
+
+ const tabs = findGlTabs();
+
+ expect(tabs.attributes('value')).toBe('0');
+
+ tabs.vm.$emit('input', 0);
+
+ await wrapper.vm.$nextTick();
+
+ expect(updateHistory).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when provided with a query param', () => {
+ it.each`
+ chart | tab
+ ${'deployments'} | ${'1'}
+ ${'pipelines'} | ${'0'}
+ ${'fake'} | ${'0'}
+ ${''} | ${'0'}
+ `('shows the correct tab for URL parameter "$chart"', ({ chart, tab }) => {
+ setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts?chart=${chart}`);
+ getParameterValues.mockImplementation((name) => {
+ expect(name).toBe('chart');
+ return chart ? [chart] : [];
+ });
+ createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } });
+ expect(findGlTabs().attributes('value')).toBe(tab);
+ });
+
+ it('should set the tab when the back button is clicked', async () => {
+ let popstateHandler;
+
+ window.addEventListener = jest.fn();
+
+ window.addEventListener.mockImplementation((event, handler) => {
+ if (event === 'popstate') {
+ popstateHandler = handler;
+ }
+ });
+
+ getParameterValues.mockImplementation((name) => {
+ expect(name).toBe('chart');
+ return [];
+ });
+
+ createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } });
+
+ expect(findGlTabs().attributes('value')).toBe('0');
+
+ getParameterValues.mockImplementationOnce((name) => {
+ expect(name).toBe('chart');
+ return ['deployments'];
+ });
+
+ popstateHandler();
+
+ await wrapper.vm.$nextTick();
+
+ expect(findGlTabs().attributes('value')).toBe('1');
+ });
});
describe('when shouldRenderDeploymentFrequencyCharts is false', () => {
diff --git a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
new file mode 100644
index 00000000000..037530ddd48
--- /dev/null
+++ b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
@@ -0,0 +1,94 @@
+import { GlSegmentedControl } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
+import CiCdAnalyticsCharts from '~/projects/pipelines/charts/components/ci_cd_analytics_charts.vue';
+import { transformedAreaChartData, chartOptions } from '../mock_data';
+
+const DEFAULT_PROPS = {
+ chartOptions,
+ charts: [
+ {
+ range: 'test range 1',
+ title: 'title 1',
+ data: transformedAreaChartData,
+ },
+ {
+ range: 'test range 2',
+ title: 'title 2',
+ data: transformedAreaChartData,
+ },
+ {
+ range: 'test range 3',
+ title: 'title 3',
+ data: transformedAreaChartData,
+ },
+ ],
+};
+
+describe('~/projects/pipelines/charts/components/ci_cd_analytics_charts.vue', () => {
+ let wrapper;
+
+ const createWrapper = (props = {}) =>
+ shallowMount(CiCdAnalyticsCharts, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...props,
+ },
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ describe('segmented control', () => {
+ let segmentedControl;
+
+ beforeEach(() => {
+ wrapper = createWrapper();
+ segmentedControl = wrapper.find(GlSegmentedControl);
+ });
+
+ it('should default to the first chart', () => {
+ expect(segmentedControl.props('checked')).toBe(0);
+ });
+
+ it('should use the title and index as values', () => {
+ const options = segmentedControl.props('options');
+ expect(options).toHaveLength(3);
+ expect(options).toEqual([
+ {
+ text: 'title 1',
+ value: 0,
+ },
+ {
+ text: 'title 2',
+ value: 1,
+ },
+ {
+ text: 'title 3',
+ value: 2,
+ },
+ ]);
+ });
+
+ it('should select a different chart on change', async () => {
+ segmentedControl.vm.$emit('input', 1);
+
+ const chart = wrapper.find(CiCdAnalyticsAreaChart);
+
+ await nextTick();
+
+ expect(chart.props('chartData')).toEqual(transformedAreaChartData);
+ expect(chart.text()).toBe('Date range: test range 2');
+ });
+ });
+
+ it('should not display charts if there are no charts', () => {
+ wrapper = createWrapper({ charts: [] });
+ expect(wrapper.find(CiCdAnalyticsAreaChart).exists()).toBe(false);
+ });
+});
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 598055d5828..c5cfe783569 100644
--- a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
@@ -1,35 +1,37 @@
-import { shallowMount } from '@vue/test-utils';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
-import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import CiCdAnalyticsCharts from '~/projects/pipelines/charts/components/ci_cd_analytics_charts.vue';
import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue';
-import {
- counts,
- timesChartData as timesChart,
- areaChartData as lastWeek,
- areaChartData as lastMonth,
- lastYearChartData as lastYear,
-} from '../mock_data';
+import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
+import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql';
+import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql';
+import { mockPipelineCount, mockPipelineStatistics } from '../mock_data';
+
+const projectPath = 'gitlab-org/gitlab';
+const localVue = createLocalVue();
+localVue.use(VueApollo);
-describe('ProjectsPipelinesChartsApp', () => {
+describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
let wrapper;
+ function createMockApolloProvider() {
+ const requestHandlers = [
+ [getPipelineCountByStatus, jest.fn().mockResolvedValue(mockPipelineCount)],
+ [getProjectPipelineStatistics, jest.fn().mockResolvedValue(mockPipelineStatistics)],
+ ];
+
+ return createMockApollo(requestHandlers);
+ }
+
beforeEach(() => {
wrapper = shallowMount(PipelineCharts, {
- propsData: {
- counts,
- timesChart,
- lastWeek,
- lastMonth,
- lastYear,
- },
provide: {
- projectPath: 'test/project',
- shouldRenderDeploymentFrequencyCharts: true,
- },
- stubs: {
- DeploymentFrequencyCharts: true,
+ projectPath,
},
+ localVue,
+ apolloProvider: createMockApolloProvider(),
});
});
@@ -43,7 +45,12 @@ describe('ProjectsPipelinesChartsApp', () => {
const list = wrapper.find(StatisticsList);
expect(list.exists()).toBe(true);
- expect(list.props('counts')).toBe(counts);
+ expect(list.props('counts')).toEqual({
+ total: 34,
+ success: 23,
+ failed: 1,
+ successRatio: (23 / (23 + 1)) * 100,
+ });
});
it('displays the commit duration chart', () => {
@@ -58,20 +65,17 @@ describe('ProjectsPipelinesChartsApp', () => {
});
describe('pipelines charts', () => {
- it('displays 3 area charts', () => {
- expect(wrapper.findAll(CiCdAnalyticsAreaChart)).toHaveLength(3);
+ it('displays the charts components', () => {
+ expect(wrapper.find(CiCdAnalyticsCharts).exists()).toBe(true);
});
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
- const charts = wrapper.findAll(CiCdAnalyticsAreaChart);
- for (let i = 0; i < charts.length; i += 1) {
- const chart = charts.at(i);
-
- expect(chart.exists()).toBeTruthy();
- expect(chart.props('chartData')).toBe(wrapper.vm.areaCharts[i].data);
- expect(chart.text()).toBe(wrapper.vm.areaCharts[i].title);
- }
+ const charts = wrapper.find(CiCdAnalyticsCharts);
+ expect(charts.props()).toEqual({
+ charts: wrapper.vm.areaCharts,
+ chartOptions: wrapper.vm.$options.areaChartOptions,
+ });
});
});
});
diff --git a/spec/frontend/projects/pipelines/charts/mock_data.js b/spec/frontend/projects/pipelines/charts/mock_data.js
index 3bc09f0b0a0..2e2c594102c 100644
--- a/spec/frontend/projects/pipelines/charts/mock_data.js
+++ b/spec/frontend/projects/pipelines/charts/mock_data.js
@@ -57,6 +57,16 @@ export const mockPipelineCount = {
},
};
+export const chartOptions = {
+ xAxis: {
+ name: 'X axis title',
+ type: 'category',
+ },
+ yAxis: {
+ name: 'Y axis title',
+ },
+};
+
export const mockPipelineStatistics = {
data: {
project: {
diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js
index 8a57930ac83..236968a3736 100644
--- a/spec/frontend/projects/settings/access_dropdown_spec.js
+++ b/spec/frontend/projects/settings/access_dropdown_spec.js
@@ -14,7 +14,6 @@ describe('AccessDropdown', () => {
`);
const $dropdown = $('#dummy-dropdown');
$dropdown.data('defaultLabel', defaultLabel);
- gon.features = { deployKeysOnProtectedBranches: true };
const options = {
$dropdown,
accessLevelsData: {
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 1fac3d07b16..2d6efe7ae83 100644
--- a/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
+++ b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
@@ -2,8 +2,8 @@ import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAxiosAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue';
import axios from '~/lib/utils/axios_utils';
+import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue';
const TEST_UPDATE_PATH = '/test/update_shared_runners';
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 c83b1852147..f9fbb1b3016 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
@@ -1,20 +1,36 @@
-import { mount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue';
-import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
+import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue';
+import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue';
describe('ServiceDeskRoot', () => {
- const endpoint = '/gitlab-org/gitlab-test/service_desk';
- const initialIncomingEmail = 'servicedeskaddress@example.com';
let axiosMock;
let wrapper;
let spy;
+ const provideData = {
+ customEmail: 'custom.email@example.com',
+ customEmailEnabled: true,
+ endpoint: '/gitlab-org/gitlab-test/service_desk',
+ initialIncomingEmail: 'servicedeskaddress@example.com',
+ initialIsEnabled: true,
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ selectedTemplate: 'Bug',
+ templates: ['Bug', 'Documentation'],
+ };
+
+ const getAlertText = () => wrapper.find(GlAlert).text();
+
+ const createComponent = () => shallowMount(ServiceDeskRoot, { provide: provideData });
+
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
+ spy = jest.spyOn(axios, 'put');
});
afterEach(() => {
@@ -25,156 +41,122 @@ describe('ServiceDeskRoot', () => {
}
});
- it('sends a request to toggle service desk off when the toggle is clicked from the on state', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+ describe('ServiceDeskSetting component', () => {
+ it('is rendered', () => {
+ wrapper = createComponent();
+
+ expect(wrapper.find(ServiceDeskSetting).props()).toEqual({
+ customEmail: provideData.customEmail,
+ customEmailEnabled: provideData.customEmailEnabled,
+ incomingEmail: provideData.initialIncomingEmail,
+ initialOutgoingName: provideData.outgoingName,
+ initialProjectKey: provideData.projectKey,
+ initialSelectedTemplate: provideData.selectedTemplate,
+ isEnabled: provideData.initialIsEnabled,
+ isTemplateSaving: false,
+ templates: provideData.templates,
+ });
+ });
+
+ describe('toggle event', () => {
+ describe('when toggling service desk on', () => {
+ beforeEach(async () => {
+ wrapper = createComponent();
- spy = jest.spyOn(axios, 'put');
+ wrapper.find(ServiceDeskSetting).vm.$emit('toggle', true);
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: true,
- initialIncomingEmail,
- endpoint,
- },
- });
+ await waitForPromises();
+ });
+
+ it('sends a request to turn service desk on', () => {
+ axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK);
- wrapper.find('button.gl-toggle').trigger('click');
+ expect(spy).toHaveBeenCalledWith(provideData.endpoint, { service_desk_enabled: true });
+ });
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: false });
+ it('shows a message when there is an error', () => {
+ axiosMock.onPut(provideData.endpoint).networkError();
+
+ expect(getAlertText()).toContain('An error occurred while enabling Service Desk.');
+ });
});
- });
- it('sends a request to toggle service desk on when the toggle is clicked from the off state', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+ describe('when toggling service desk off', () => {
+ beforeEach(async () => {
+ wrapper = createComponent();
- spy = jest.spyOn(axios, 'put');
+ wrapper.find(ServiceDeskSetting).vm.$emit('toggle', false);
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: false,
- initialIncomingEmail: '',
- endpoint,
- },
- });
+ await waitForPromises();
+ });
- wrapper.find('button.gl-toggle').trigger('click');
+ it('sends a request to turn service desk off', () => {
+ axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK);
- return wrapper.vm.$nextTick(() => {
- expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: true });
- });
- });
+ expect(spy).toHaveBeenCalledWith(provideData.endpoint, { service_desk_enabled: false });
+ });
- it('shows an error message when there is an issue toggling service desk on', () => {
- axiosMock.onPut(endpoint).networkError();
+ it('shows a message when there is an error', () => {
+ axiosMock.onPut(provideData.endpoint).networkError();
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: false,
- initialIncomingEmail: '',
- endpoint,
- },
+ expect(getAlertText()).toContain('An error occurred while disabling Service Desk.');
+ });
+ });
});
- wrapper.find('button.gl-toggle').trigger('click');
+ describe('save event', () => {
+ describe('successful request', () => {
+ beforeEach(async () => {
+ axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK);
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(wrapper.html()).toContain('An error occurred while enabling Service Desk.');
- });
- });
+ wrapper = createComponent();
- it('sends a request to update template when the "Save template" button is clicked', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+ const payload = {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ };
- spy = jest.spyOn(axios, 'put');
+ wrapper.find(ServiceDeskSetting).vm.$emit('save', payload);
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: true,
- endpoint,
- initialIncomingEmail,
- selectedTemplate: 'Bug',
- outgoingName: 'GitLab Support Bot',
- templates: ['Bug', 'Documentation'],
- projectKey: 'key',
- },
- });
+ await waitForPromises();
+ });
- wrapper.find('button.btn-success').trigger('click');
+ it('sends a request to update template', async () => {
+ expect(spy).toHaveBeenCalledWith(provideData.endpoint, {
+ issue_template_key: 'Bug',
+ outgoing_name: 'GitLab Support Bot',
+ project_key: 'key',
+ service_desk_enabled: true,
+ });
+ });
- return wrapper.vm.$nextTick(() => {
- expect(spy).toHaveBeenCalledWith(endpoint, {
- issue_template_key: 'Bug',
- outgoing_name: 'GitLab Support Bot',
- project_key: 'key',
- service_desk_enabled: true,
+ it('shows success message', () => {
+ expect(getAlertText()).toContain('Changes saved.');
+ });
});
- });
- });
- it('saves the template when the "Save template" button is clicked', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
-
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: true,
- endpoint,
- initialIncomingEmail,
- selectedTemplate: 'Bug',
- templates: ['Bug', 'Documentation'],
- },
- });
+ describe('unsuccessful request', () => {
+ beforeEach(async () => {
+ axiosMock.onPut(provideData.endpoint).networkError();
- wrapper.find('button.btn-success').trigger('click');
+ wrapper = createComponent();
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(wrapper.html()).toContain('Changes saved.');
- });
- });
+ const payload = {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ };
- it('shows an error message when there is an issue saving the template', () => {
- axiosMock.onPut(endpoint).networkError();
-
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: true,
- endpoint,
- initialIncomingEmail,
- selectedTemplate: 'Bug',
- templates: ['Bug', 'Documentation'],
- },
- });
+ wrapper.find(ServiceDeskSetting).vm.$emit('save', payload);
- wrapper.find('button.btn-success').trigger('click');
+ await waitForPromises();
+ });
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(wrapper.html()).toContain('An error occured while saving changes:');
+ it('shows an error message', () => {
+ expect(getAlertText()).toContain('An error occured while saving changes:');
+ });
});
- });
-
- it('passes customEmail through updatedCustomEmail correctly', () => {
- const customEmail = 'foo';
-
- wrapper = mount(ServiceDeskRoot, {
- propsData: {
- initialIsEnabled: true,
- endpoint,
- customEmail,
- },
});
-
- expect(wrapper.find(ServiceDeskSetting).props('customEmail')).toEqual(customEmail);
});
});
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
index ddd9a7b2fad..f6744f4971e 100644
--- a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
@@ -1,63 +1,68 @@
+import { GlButton, GlFormSelect, GlLoadingIcon, GlToggle } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
-import { GlLoadingIcon } from '@gitlab/ui';
-import eventHub from '~/projects/settings_service_desk/event_hub';
+import { nextTick } from 'vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('ServiceDeskSetting', () => {
let wrapper;
+ const findButton = () => wrapper.find(GlButton);
+ const findClipboardButton = () => wrapper.find(ClipboardButton);
+ const findIncomingEmail = () => wrapper.findByTestId('incoming-email');
+ const findIncomingEmailLabel = () => wrapper.findByTestId('incoming-email-describer');
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findTemplateDropdown = () => wrapper.find(GlFormSelect);
+ const findToggle = () => wrapper.find(GlToggle);
+
+ const createComponent = ({ props = {}, mountFunction = shallowMount } = {}) =>
+ extendedWrapper(
+ mountFunction(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ ...props,
+ },
+ }),
+ );
+
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
- const findTemplateDropdown = () => wrapper.find('#service-desk-template-select');
- const findIncomingEmail = () => wrapper.find('[data-testid="incoming-email"]');
-
describe('when isEnabled=true', () => {
describe('only isEnabled', () => {
describe('as project admin', () => {
beforeEach(() => {
- wrapper = shallowMount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- },
- });
+ wrapper = createComponent();
});
it('should see activation checkbox', () => {
- expect(wrapper.find('#service-desk-checkbox').exists()).toBe(true);
+ expect(findToggle().exists()).toBe(true);
});
it('should see main panel with the email info', () => {
- expect(wrapper.find('#incoming-email-describer').exists()).toBe(true);
+ expect(findIncomingEmailLabel().exists()).toBe(true);
});
it('should see loading spinner and not the incoming email', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
expect(findIncomingEmail().exists()).toBe(false);
});
});
});
describe('service desk toggle', () => {
- it('emits an event to turn on Service Desk when clicked', () => {
- const eventSpy = jest.fn();
- eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy);
-
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: false,
- },
- });
+ it('emits an event to turn on Service Desk when clicked', async () => {
+ wrapper = createComponent();
- wrapper.find('#service-desk-checkbox').trigger('click');
+ findToggle().vm.$emit('change', true);
- expect(eventSpy).toHaveBeenCalledWith(true);
+ await nextTick();
- eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy);
- eventSpy.mockRestore();
+ expect(wrapper.emitted('toggle')[0]).toEqual([true]);
});
});
@@ -65,23 +70,23 @@ describe('ServiceDeskSetting', () => {
const incomingEmail = 'foo@bar.com';
beforeEach(() => {
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- incomingEmail,
- },
+ wrapper = createComponent({
+ props: { incomingEmail },
});
});
it('should see email and not the loading spinner', () => {
expect(findIncomingEmail().element.value).toEqual(incomingEmail);
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('renders a copy to clipboard button', () => {
- expect(wrapper.find('.qa-clipboard-button').exists()).toBe(true);
- expect(wrapper.find('.qa-clipboard-button').element.dataset.clipboardText).toBe(
- incomingEmail,
+ expect(findClipboardButton().exists()).toBe(true);
+ expect(findClipboardButton().props()).toEqual(
+ expect.objectContaining({
+ title: 'Copy',
+ text: incomingEmail,
+ }),
);
});
});
@@ -92,12 +97,8 @@ describe('ServiceDeskSetting', () => {
const customEmail = 'custom@bar.com';
beforeEach(() => {
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- incomingEmail,
- customEmail,
- },
+ wrapper = createComponent({
+ props: { incomingEmail, customEmail },
});
});
@@ -110,12 +111,8 @@ describe('ServiceDeskSetting', () => {
const email = 'foo@bar.com';
beforeEach(() => {
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- incomingEmail: email,
- customEmail: email,
- },
+ wrapper = createComponent({
+ props: { incomingEmail: email, customEmail: email },
});
});
@@ -127,21 +124,13 @@ describe('ServiceDeskSetting', () => {
describe('templates dropdown', () => {
it('renders a dropdown to choose a template', () => {
- wrapper = shallowMount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- },
- });
+ wrapper = createComponent();
- expect(wrapper.find('#service-desk-template-select').exists()).toBe(true);
+ expect(findTemplateDropdown().exists()).toBe(true);
});
it('renders a dropdown with a default value of ""', () => {
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- },
- });
+ wrapper = createComponent({ mountFunction: mount });
expect(findTemplateDropdown().element.value).toEqual('');
});
@@ -149,23 +138,18 @@ describe('ServiceDeskSetting', () => {
it('renders a dropdown with a value of "Bug" when it is the initial value', () => {
const templates = ['Bug', 'Documentation', 'Security release'];
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- initialSelectedTemplate: 'Bug',
- templates,
- },
+ wrapper = createComponent({
+ props: { initialSelectedTemplate: 'Bug', templates },
+ mountFunction: mount,
});
expect(findTemplateDropdown().element.value).toEqual('Bug');
});
it('renders a dropdown with no options when the project has no templates', () => {
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- templates: [],
- },
+ wrapper = createComponent({
+ props: { templates: [] },
+ mountFunction: mount,
});
// The dropdown by default has one empty option
@@ -174,11 +158,10 @@ describe('ServiceDeskSetting', () => {
it('renders a dropdown with options when the project has templates', () => {
const templates = ['Bug', 'Documentation', 'Security release'];
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- templates,
- },
+
+ wrapper = createComponent({
+ props: { templates },
+ mountFunction: mount,
});
// An empty-named template is prepended so the user can select no template
@@ -199,78 +182,59 @@ describe('ServiceDeskSetting', () => {
describe('save button', () => {
it('renders a save button to save a template', () => {
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- },
- });
+ wrapper = createComponent();
- expect(wrapper.find('button.btn-success').text()).toContain('Save changes');
+ expect(findButton().text()).toContain('Save changes');
});
- it('emits a save event with the chosen template when the save button is clicked', () => {
- const eventSpy = jest.fn();
- eventHub.$on('serviceDeskTemplateSave', eventSpy);
-
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
+ it('emits a save event with the chosen template when the save button is clicked', async () => {
+ wrapper = createComponent({
+ props: {
initialSelectedTemplate: 'Bug',
initialOutgoingName: 'GitLab Support Bot',
initialProjectKey: 'key',
},
});
- wrapper.find('button.btn-success').trigger('click');
+ findButton().vm.$emit('click');
+
+ await nextTick();
- expect(eventSpy).toHaveBeenCalledWith({
+ const payload = {
selectedTemplate: 'Bug',
outgoingName: 'GitLab Support Bot',
projectKey: 'key',
- });
+ };
- eventHub.$off('serviceDeskTemplateSave', eventSpy);
- eventSpy.mockRestore();
+ expect(wrapper.emitted('save')[0]).toEqual([payload]);
});
});
describe('when isEnabled=false', () => {
beforeEach(() => {
- wrapper = shallowMount(ServiceDeskSetting, {
- propsData: {
- isEnabled: false,
- },
+ wrapper = createComponent({
+ props: { isEnabled: false },
});
});
it('does not render email panel', () => {
- expect(wrapper.find('#incoming-email-describer').exists()).toBe(false);
+ expect(findIncomingEmailLabel().exists()).toBe(false);
});
it('does not render template dropdown', () => {
- expect(wrapper.find('#service-desk-template-select').exists()).toBe(false);
+ expect(findTemplateDropdown().exists()).toBe(false);
});
it('does not render template save button', () => {
- expect(wrapper.find('button.btn-success').exists()).toBe(false);
+ expect(findButton().exists()).toBe(false);
});
- it('emits an event to turn on Service Desk when the toggle is clicked', () => {
- const eventSpy = jest.fn();
- eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy);
-
- wrapper = mount(ServiceDeskSetting, {
- propsData: {
- isEnabled: true,
- },
- });
-
- wrapper.find('#service-desk-checkbox').trigger('click');
+ it('emits an event to turn on Service Desk when the toggle is clicked', async () => {
+ findToggle().vm.$emit('change', false);
- expect(eventSpy).toHaveBeenCalledWith(false);
+ await nextTick();
- eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy);
- eventSpy.mockRestore();
+ expect(wrapper.emitted('toggle')[0]).toEqual([false]);
});
});
});
diff --git a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js
deleted file mode 100644
index d5340df03fe..00000000000
--- a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import AxiosMockAdapter from 'axios-mock-adapter';
-import ServiceDeskService from '~/projects/settings_service_desk/services/service_desk_service';
-import axios from '~/lib/utils/axios_utils';
-import httpStatusCodes from '~/lib/utils/http_status';
-
-describe('ServiceDeskService', () => {
- const endpoint = `/gitlab-org/gitlab-test/service_desk`;
- const dummyResponse = { message: 'Dummy response' };
- const errorMessage = 'Network Error';
- let axiosMock;
- let service;
-
- beforeEach(() => {
- axiosMock = new AxiosMockAdapter(axios);
- service = new ServiceDeskService(endpoint);
- });
-
- afterEach(() => {
- axiosMock.restore();
- });
-
- describe('toggleServiceDesk', () => {
- it('makes a request to set service desk', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
-
- return service.toggleServiceDesk(true).then((response) => {
- expect(response.data).toEqual(dummyResponse);
- });
- });
-
- it('fails on error response', () => {
- axiosMock.onPut(endpoint).networkError();
-
- return service.toggleServiceDesk(true).catch((error) => {
- expect(error.message).toBe(errorMessage);
- });
- });
-
- it('makes a request with the expected body', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
-
- const spy = jest.spyOn(axios, 'put');
-
- service.toggleServiceDesk(true);
-
- expect(spy).toHaveBeenCalledWith(endpoint, {
- service_desk_enabled: true,
- });
-
- spy.mockRestore();
- });
- });
-
- describe('updateTemplate', () => {
- it('makes a request to update template', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
-
- return service
- .updateTemplate(
- {
- selectedTemplate: 'Bug',
- outgoingName: 'GitLab Support Bot',
- },
- true,
- )
- .then((response) => {
- expect(response.data).toEqual(dummyResponse);
- });
- });
-
- it('fails on error response', () => {
- axiosMock.onPut(endpoint).networkError();
-
- return service
- .updateTemplate(
- {
- selectedTemplate: 'Bug',
- outgoingName: 'GitLab Support Bot',
- },
- true,
- )
- .catch((error) => {
- expect(error.message).toBe(errorMessage);
- });
- });
-
- it('makes a request with the expected body', () => {
- axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
-
- const spy = jest.spyOn(axios, 'put');
-
- service.updateTemplate(
- {
- selectedTemplate: 'Bug',
- outgoingName: 'GitLab Support Bot',
- projectKey: 'key',
- },
- true,
- );
-
- expect(spy).toHaveBeenCalledWith(endpoint, {
- issue_template_key: 'Bug',
- outgoing_name: 'GitLab Support Bot',
- project_key: 'key',
- service_desk_enabled: true,
- });
-
- spy.mockRestore();
- });
- });
-});
diff --git a/spec/frontend/prometheus_alerts/components/reset_key_spec.js b/spec/frontend/prometheus_alerts/components/reset_key_spec.js
index 489586a60fe..edf5297cc6a 100644
--- a/spec/frontend/prometheus_alerts/components/reset_key_spec.js
+++ b/spec/frontend/prometheus_alerts/components/reset_key_spec.js
@@ -1,10 +1,10 @@
+import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { GlModal } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
import ResetKey from '~/prometheus_alerts/components/reset_key.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import axios from '~/lib/utils/axios_utils';
describe('ResetKey', () => {
let mock;
diff --git a/spec/frontend/prometheus_metrics/custom_metrics_spec.js b/spec/frontend/prometheus_metrics/custom_metrics_spec.js
index 1244d7342ad..3e3d4ee361a 100644
--- a/spec/frontend/prometheus_metrics/custom_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/custom_metrics_spec.js
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
-import CustomMetrics from '~/prometheus_metrics/custom_metrics';
import axios from '~/lib/utils/axios_utils';
import PANEL_STATE from '~/prometheus_metrics/constants';
+import CustomMetrics from '~/prometheus_metrics/custom_metrics';
import { metrics1 as metrics } from './mock_data';
describe('PrometheusMetrics', () => {
diff --git a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
index f6b2780e167..722a5274ad4 100644
--- a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
import PANEL_STATE from '~/prometheus_metrics/constants';
+import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
import { metrics2 as metrics, missingVarMetrics } from './mock_data';
describe('PrometheusMetrics', () => {
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 7efb6e9ba4a..27ada131ed6 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -1,11 +1,11 @@
-import Vuex from 'vuex';
+import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlIcon } from '@gitlab/ui';
+import Vuex from 'vuex';
import { trimText } from 'helpers/text_helper';
-import { sprintf } from '~/locale';
import { ENTER_KEY } from '~/lib/utils/keys';
+import { sprintf } from '~/locale';
import RefSelector from '~/ref/components/ref_selector.vue';
import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants';
import createStore from '~/ref/stores/';
diff --git a/spec/frontend/ref/stores/actions_spec.js b/spec/frontend/ref/stores/actions_spec.js
index 32966354c95..11acec27165 100644
--- a/spec/frontend/ref/stores/actions_spec.js
+++ b/spec/frontend/ref/stores/actions_spec.js
@@ -1,7 +1,7 @@
import testAction from 'helpers/vuex_action_helper';
-import createState from '~/ref/stores/state';
import * as actions from '~/ref/stores/actions';
import * as types from '~/ref/stores/mutation_types';
+import createState from '~/ref/stores/state';
let mockBranchesReturnValue;
let mockTagsReturnValue;
diff --git a/spec/frontend/ref/stores/mutations_spec.js b/spec/frontend/ref/stores/mutations_spec.js
index 78117436c33..cda13089766 100644
--- a/spec/frontend/ref/stores/mutations_spec.js
+++ b/spec/frontend/ref/stores/mutations_spec.js
@@ -1,7 +1,7 @@
-import createState from '~/ref/stores/state';
-import mutations from '~/ref/stores/mutations';
-import * as types from '~/ref/stores/mutation_types';
import { X_TOTAL_HEADER } from '~/ref/constants';
+import * as types from '~/ref/stores/mutation_types';
+import mutations from '~/ref/stores/mutations';
+import createState from '~/ref/stores/state';
describe('Ref selector Vuex store mutations', () => {
let state;
diff --git a/spec/frontend/registry/explorer/components/delete_button_spec.js b/spec/frontend/registry/explorer/components/delete_button_spec.js
index cd43e97009b..a557d9afacc 100644
--- a/spec/frontend/registry/explorer/components/delete_button_spec.js
+++ b/spec/frontend/registry/explorer/components/delete_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import component from '~/registry/explorer/components/delete_button.vue';
diff --git a/spec/frontend/registry/explorer/components/delete_image_spec.js b/spec/frontend/registry/explorer/components/delete_image_spec.js
new file mode 100644
index 00000000000..9a0d070e42b
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/delete_image_spec.js
@@ -0,0 +1,152 @@
+import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import component from '~/registry/explorer/components/delete_image.vue';
+import { GRAPHQL_PAGE_SIZE } from '~/registry/explorer/constants/index';
+import deleteContainerRepositoryMutation from '~/registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
+import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
+
+describe('Delete Image', () => {
+ let wrapper;
+ const id = '1';
+ const storeMock = {
+ readQuery: jest.fn().mockReturnValue({
+ containerRepository: {
+ status: 'foo',
+ },
+ }),
+ writeQuery: jest.fn(),
+ };
+
+ const updatePayload = {
+ data: {
+ destroyContainerRepository: {
+ containerRepository: {
+ status: 'baz',
+ },
+ },
+ },
+ };
+
+ const findButton = () => wrapper.find('button');
+
+ const mountComponent = ({
+ propsData = { id },
+ mutate = jest.fn().mockResolvedValue({}),
+ } = {}) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
+ scopedSlots: {
+ default: '<button @click="props.doDelete">test</button>',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('executes apollo mutate on doDelete', () => {
+ const mutate = jest.fn().mockResolvedValue({});
+ mountComponent({ mutate });
+
+ wrapper.vm.doDelete();
+
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: deleteContainerRepositoryMutation,
+ variables: {
+ id,
+ },
+ update: undefined,
+ });
+ });
+
+ it('on success emits the correct events', async () => {
+ const mutate = jest.fn().mockResolvedValue({});
+ mountComponent({ mutate });
+
+ wrapper.vm.doDelete();
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('start')).toEqual([[]]);
+ expect(wrapper.emitted('success')).toEqual([[]]);
+ expect(wrapper.emitted('end')).toEqual([[]]);
+ });
+
+ it('when a payload contains an error emits an error event', async () => {
+ const mutate = jest
+ .fn()
+ .mockResolvedValue({ data: { destroyContainerRepository: { errors: ['foo'] } } });
+
+ mountComponent({ mutate });
+ wrapper.vm.doDelete();
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[['foo']]]);
+ });
+
+ it('when the api call errors emits an error event', async () => {
+ const mutate = jest.fn().mockRejectedValue('error');
+
+ mountComponent({ mutate });
+ wrapper.vm.doDelete();
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[['error']]]);
+ });
+
+ it('uses the update function, when the prop is set to true', () => {
+ const mutate = jest.fn().mockResolvedValue({});
+
+ mountComponent({ mutate, propsData: { id, useUpdateFn: true } });
+ wrapper.vm.doDelete();
+
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: deleteContainerRepositoryMutation,
+ variables: {
+ id,
+ },
+ update: wrapper.vm.updateImageStatus,
+ });
+ });
+
+ it('updateImage status reads and write to the cache', () => {
+ mountComponent();
+
+ const variables = {
+ id,
+ first: GRAPHQL_PAGE_SIZE,
+ };
+
+ wrapper.vm.updateImageStatus(storeMock, updatePayload);
+
+ expect(storeMock.readQuery).toHaveBeenCalledWith({
+ query: getContainerRepositoryDetailsQuery,
+ variables,
+ });
+ expect(storeMock.writeQuery).toHaveBeenCalledWith({
+ query: getContainerRepositoryDetailsQuery,
+ variables,
+ data: {
+ containerRepository: {
+ status: updatePayload.data.destroyContainerRepository.containerRepository.status,
+ },
+ },
+ });
+ });
+
+ it('binds the doDelete function to the default scoped slot', () => {
+ const mutate = jest.fn().mockResolvedValue({});
+ mountComponent({ mutate });
+ findButton().trigger('click');
+ expect(mutate).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js
index 6a7fbbe367a..c2a2a4e06ea 100644
--- a/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/details_page/delete_alert.vue';
import {
DELETE_TAG_SUCCESS_MESSAGE,
diff --git a/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js b/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js
index 636e0a285a6..8fe659694ba 100644
--- a/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js
@@ -1,9 +1,11 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/details_page/delete_modal.vue';
import {
REMOVE_TAG_CONFIRMATION_TEXT,
REMOVE_TAGS_CONFIRMATION_TEXT,
+ DELETE_IMAGE_CONFIRMATION_TITLE,
+ DELETE_IMAGE_CONFIRMATION_TEXT,
} from '~/registry/explorer/constants';
import { GlModal } from '../../stubs';
@@ -35,13 +37,13 @@ describe('Delete Modal', () => {
describe('events', () => {
it.each`
- glEvent | localEvent
- ${'ok'} | ${'confirmDelete'}
- ${'cancel'} | ${'cancelDelete'}
+ glEvent | localEvent
+ ${'primary'} | ${'confirmDelete'}
+ ${'cancel'} | ${'cancelDelete'}
`('GlModal $glEvent emits $localEvent', ({ glEvent, localEvent }) => {
mountComponent();
findModal().vm.$emit(glEvent);
- expect(wrapper.emitted(localEvent)).toBeTruthy();
+ expect(wrapper.emitted(localEvent)).toEqual([[]]);
});
});
@@ -53,27 +55,51 @@ describe('Delete Modal', () => {
});
});
- describe('itemsToBeDeleted contains one element', () => {
- beforeEach(() => {
- mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] });
- });
- it(`has the correct description`, () => {
- expect(findDescription().text()).toBe(REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo'));
+ describe('when we are deleting images', () => {
+ it('has the correct title', () => {
+ mountComponent({ deleteImage: true });
+
+ expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TITLE);
});
- it('has the correct action', () => {
- expect(wrapper.text()).toContain('Remove tag');
+
+ it('has the correct description', () => {
+ mountComponent({ deleteImage: true });
+
+ expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TEXT);
});
});
- describe('itemsToBeDeleted contains more than element', () => {
- beforeEach(() => {
- mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] });
- });
- it(`has the correct description`, () => {
- expect(findDescription().text()).toBe(REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2'));
+ describe('when we are deleting tags', () => {
+ describe('itemsToBeDeleted contains one element', () => {
+ beforeEach(() => {
+ mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] });
+ });
+
+ it(`has the correct description`, () => {
+ expect(findDescription().text()).toBe(
+ REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo'),
+ );
+ });
+
+ it('has the correct title', () => {
+ expect(wrapper.text()).toContain('Remove tag');
+ });
});
- it('has the correct action', () => {
- expect(wrapper.text()).toContain('Remove tags');
+
+ describe('itemsToBeDeleted contains more than element', () => {
+ beforeEach(() => {
+ mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] });
+ });
+
+ it(`has the correct description`, () => {
+ expect(findDescription().text()).toBe(
+ REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2'),
+ );
+ });
+
+ it('has the correct title', () => {
+ expect(wrapper.text()).toContain('Remove tags');
+ });
});
});
});
diff --git a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
index 337235e3de5..3fa3a2ae1de 100644
--- a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
@@ -1,7 +1,6 @@
+import { GlSprintf, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlSprintf } from '@gitlab/ui';
import { useFakeDate } from 'helpers/fake_date';
-import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import component from '~/registry/explorer/components/details_page/details_header.vue';
import {
DETAILS_PAGE_TITLE,
@@ -15,6 +14,7 @@ import {
CLEANUP_ONGOING_TOOLTIP,
CLEANUP_UNFINISHED_TOOLTIP,
} from '~/registry/explorer/constants';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
describe('Details Header', () => {
let wrapper;
@@ -23,6 +23,7 @@ describe('Details Header', () => {
name: 'foo',
updatedAt: '2020-11-03T13:29:21Z',
tagsCount: 10,
+ canDelete: true,
project: {
visibility: 'public',
containerExpirationPolicy: {
@@ -36,8 +37,10 @@ describe('Details Header', () => {
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findLastUpdatedAndVisibility = () => findByTestId('updated-and-visibility');
+ const findTitle = () => findByTestId('title');
const findTagsCount = () => findByTestId('tags-count');
const findCleanup = () => findByTestId('cleanup');
+ const findDeleteButton = () => wrapper.find(GlButton);
const waitForMetadataItems = async () => {
// Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
@@ -45,11 +48,9 @@ describe('Details Header', () => {
await wrapper.vm.$nextTick();
};
- const mountComponent = (image = defaultImage) => {
+ const mountComponent = (propsData = { image: defaultImage }) => {
wrapper = shallowMount(component, {
- propsData: {
- image,
- },
+ propsData,
stubs: {
GlSprintf,
TitleArea,
@@ -63,13 +64,65 @@ describe('Details Header', () => {
});
it('has the correct title ', () => {
- mountComponent({ ...defaultImage, name: '' });
- expect(wrapper.text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
+ mountComponent({ image: { ...defaultImage, name: '' } });
+ expect(findTitle().text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
});
it('shows imageName in the title', () => {
mountComponent();
- expect(wrapper.text()).toContain('foo');
+ expect(findTitle().text()).toContain('foo');
+ });
+
+ describe('delete button', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findDeleteButton().exists()).toBe(true);
+ });
+
+ it('is hidden while loading', () => {
+ mountComponent({ image: defaultImage, metadataLoading: true });
+
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findDeleteButton().text()).toBe('Delete');
+ });
+
+ it('has the correct props', () => {
+ mountComponent();
+
+ expect(findDeleteButton().props()).toMatchObject({
+ variant: 'danger',
+ disabled: false,
+ });
+ });
+
+ it('emits the correct event', () => {
+ mountComponent();
+
+ findDeleteButton().vm.$emit('click');
+
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+
+ it.each`
+ canDelete | disabled | isDisabled
+ ${true} | ${false} | ${false}
+ ${true} | ${true} | ${true}
+ ${false} | ${false} | ${true}
+ ${false} | ${true} | ${true}
+ `(
+ 'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled',
+ ({ canDelete, disabled, isDisabled }) => {
+ mountComponent({ image: { ...defaultImage, canDelete }, disabled });
+
+ expect(findDeleteButton().props('disabled')).toBe(isDisabled);
+ },
+ );
});
describe('metadata items', () => {
@@ -82,7 +135,7 @@ describe('Details Header', () => {
});
it('when there is one tag has the correct text', async () => {
- mountComponent({ ...defaultImage, tagsCount: 1 });
+ mountComponent({ image: { ...defaultImage, tagsCount: 1 } });
await waitForMetadataItems();
expect(findTagsCount().props('text')).toBe('1 tag');
@@ -124,10 +177,12 @@ describe('Details Header', () => {
'when the status is $status the text is $text and the tooltip is $tooltip',
async ({ status, text, tooltip }) => {
mountComponent({
- ...defaultImage,
- expirationPolicyCleanupStatus: status,
- project: {
- containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
+ image: {
+ ...defaultImage,
+ expirationPolicyCleanupStatus: status,
+ project: {
+ containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
+ },
},
});
await waitForMetadataItems();
@@ -156,7 +211,7 @@ describe('Details Header', () => {
expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye');
});
it('shows an eye slashed when the project is not public', async () => {
- mountComponent({ ...defaultImage, project: { visibility: 'private' } });
+ mountComponent({ image: { ...defaultImage, project: { visibility: 'private' } } });
await waitForMetadataItems();
expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash');
diff --git a/spec/frontend/registry/explorer/components/details_page/empty_state_spec.js b/spec/frontend/registry/explorer/components/details_page/empty_state_spec.js
new file mode 100644
index 00000000000..14b15945631
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/empty_state_spec.js
@@ -0,0 +1,54 @@
+import { GlEmptyState } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/registry/explorer/components/details_page/empty_state.vue';
+import {
+ NO_TAGS_TITLE,
+ NO_TAGS_MESSAGE,
+ MISSING_OR_DELETED_IMAGE_TITLE,
+ MISSING_OR_DELETED_IMAGE_MESSAGE,
+} from '~/registry/explorer/constants';
+
+describe('EmptyTagsState component', () => {
+ let wrapper;
+
+ const findEmptyState = () => wrapper.find(GlEmptyState);
+
+ const mountComponent = (propsData) => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlEmptyState,
+ },
+ propsData,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('contains gl-empty-state', () => {
+ mountComponent();
+ expect(findEmptyState().exists()).toBe(true);
+ });
+
+ it.each`
+ isEmptyImage | title | description
+ ${false} | ${NO_TAGS_TITLE} | ${NO_TAGS_MESSAGE}
+ ${true} | ${MISSING_OR_DELETED_IMAGE_TITLE} | ${MISSING_OR_DELETED_IMAGE_MESSAGE}
+ `(
+ 'when isEmptyImage is $isEmptyImage has the correct props',
+ ({ isEmptyImage, title, description }) => {
+ mountComponent({
+ noContainersImage: 'foo',
+ isEmptyImage,
+ });
+
+ expect(findEmptyState().props()).toMatchObject({
+ title,
+ description,
+ svgPath: 'foo',
+ });
+ },
+ );
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js b/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js
deleted file mode 100644
index 09afd9d2d84..00000000000
--- a/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlEmptyState } from '@gitlab/ui';
-import component from '~/registry/explorer/components/details_page/empty_tags_state.vue';
-import {
- EMPTY_IMAGE_REPOSITORY_TITLE,
- EMPTY_IMAGE_REPOSITORY_MESSAGE,
-} from '~/registry/explorer/constants';
-
-describe('EmptyTagsState component', () => {
- let wrapper;
-
- const findEmptyState = () => wrapper.find(GlEmptyState);
-
- const mountComponent = () => {
- wrapper = shallowMount(component, {
- stubs: {
- GlEmptyState,
- },
- propsData: {
- noContainersImage: 'foo',
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('contains gl-empty-state', () => {
- mountComponent();
- expect(findEmptyState().exists()).toBe(true);
- });
-
- it('has the correct props', () => {
- mountComponent();
- expect(findEmptyState().props()).toMatchObject({
- title: EMPTY_IMAGE_REPOSITORY_TITLE,
- description: EMPTY_IMAGE_REPOSITORY_MESSAGE,
- svgPath: 'foo',
- });
- });
-});
diff --git a/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js
index 17821d8be31..af8a23e412c 100644
--- a/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue';
import { DELETE_ALERT_TITLE, DELETE_ALERT_LINK_TEXT } from '~/registry/explorer/constants';
diff --git a/spec/frontend/registry/explorer/components/details_page/status_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/status_alert_spec.js
new file mode 100644
index 00000000000..b079883cefd
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/status_alert_spec.js
@@ -0,0 +1,57 @@
+import { GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/registry/explorer/components/details_page/status_alert.vue';
+import {
+ DELETE_SCHEDULED,
+ DELETE_FAILED,
+ PACKAGE_DELETE_HELP_PAGE_PATH,
+ SCHEDULED_FOR_DELETION_STATUS_TITLE,
+ SCHEDULED_FOR_DELETION_STATUS_MESSAGE,
+ FAILED_DELETION_STATUS_TITLE,
+ FAILED_DELETION_STATUS_MESSAGE,
+} from '~/registry/explorer/constants';
+
+describe('Status Alert', () => {
+ let wrapper;
+
+ const findLink = () => wrapper.find(GlLink);
+ const findAlert = () => wrapper.find(GlAlert);
+ const findMessage = () => wrapper.find('[data-testid="message"]');
+
+ const mountComponent = (propsData) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ 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}
+ ${DELETE_FAILED} | ${FAILED_DELETION_STATUS_TITLE} | ${'warning'} | ${FAILED_DELETION_STATUS_MESSAGE} | ${''}
+ `(
+ `when the status is $status, title is $title, variant is $variant, message is $message and the link is $link`,
+ ({ status, title, variant, message, link }) => {
+ mountComponent({ status });
+
+ expect(findMessage().text()).toMatchInterpolatedText(message);
+ expect(findAlert().props()).toMatchObject({
+ title,
+ variant,
+ });
+ if (link) {
+ expect(findLink().attributes()).toMatchObject({
+ target: '_blank',
+ href: link,
+ });
+ }
+ },
+ );
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
index c2efc71c159..8b70f84c1bd 100644
--- a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
@@ -1,12 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormCheckbox, GlSprintf, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import component from '~/registry/explorer/components/details_page/tags_list_row.vue';
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
-import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+import component from '~/registry/explorer/components/details_page/tags_list_row.vue';
import {
REMOVE_TAG_BUTTON_TITLE,
REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
@@ -14,6 +11,9 @@ import {
NOT_AVAILABLE_TEXT,
NOT_AVAILABLE_SIZE,
} from '~/registry/explorer/constants/index';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { tagsMock } from '../../mock_data';
import { ListItem } from '../../stubs';
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
index 413795a7a57..dc6760a17bd 100644
--- a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/details_page/tags_list.vue';
import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue';
import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index';
@@ -70,18 +70,25 @@ describe('Tags List', () => {
});
});
- it('is disabled when no item is selected', () => {
- mountComponent();
+ it.each`
+ disabled | doSelect | buttonDisabled
+ ${true} | ${false} | ${'true'}
+ ${true} | ${true} | ${'true'}
+ ${false} | ${false} | ${'true'}
+ ${false} | ${true} | ${undefined}
+ `(
+ 'is $buttonDisabled that the button is disabled when the component disabled state is $disabled and is $doSelect that the user selected a tag',
+ async ({ disabled, buttonDisabled, doSelect }) => {
+ mountComponent({ tags, disabled, isMobile: false });
- expect(findDeleteButton().attributes('disabled')).toBe('true');
- });
+ if (doSelect) {
+ findTagsListRow().at(0).vm.$emit('select');
+ await wrapper.vm.$nextTick();
+ }
- it('is enabled when at least one item is selected', async () => {
- mountComponent();
- findTagsListRow().at(0).vm.$emit('select');
- await wrapper.vm.$nextTick();
- expect(findDeleteButton().attributes('disabled')).toBe(undefined);
- });
+ expect(findDeleteButton().attributes('disabled')).toBe(buttonDisabled);
+ },
+ );
it('click event emits a deleted event with selected items', () => {
mountComponent();
@@ -100,12 +107,13 @@ describe('Tags List', () => {
});
it('the correct props are bound to it', () => {
- mountComponent();
+ mountComponent({ tags, disabled: true });
const rows = findTagsListRow();
expect(rows.at(0).attributes()).toMatchObject({
first: 'true',
+ disabled: 'true',
});
});
diff --git a/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js b/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js
index 74b9ea5fd96..8ca8fca65ed 100644
--- a/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/cli_commands_spec.js
@@ -1,9 +1,7 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlDropdown } from '@gitlab/ui';
-import Tracking from '~/tracking';
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import QuickstartDropdown from '~/registry/explorer/components/list_page/cli_commands.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
import {
QUICK_START,
LOGIN_COMMAND_LABEL,
@@ -13,6 +11,8 @@ import {
PUSH_COMMAND_LABEL,
COPY_PUSH_TITLE,
} from '~/registry/explorer/constants';
+import Tracking from '~/tracking';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
import { dockerCommands } from '../../mock_data';
diff --git a/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js b/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js
index 1ba2036dc34..989a60625e2 100644
--- a/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/group_empty_state_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
-import { GlEmptyState } from '../../stubs';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import groupEmptyState from '~/registry/explorer/components/list_page/group_empty_state.vue';
+import { GlEmptyState } from '../../stubs';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
index a06c4795b2e..d6ee871341b 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
@@ -1,11 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
-import ListItem from '~/vue_shared/components/registry/list_item.vue';
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
+import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
import {
ROW_SCHEDULED_FOR_DELETION,
LIST_DELETE_BUTTON_DISABLED,
@@ -15,8 +13,10 @@ import {
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
} from '~/registry/explorer/constants';
-import { RouterLink } from '../../stubs';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { imagesListResponse } from '../../mock_data';
+import { RouterLink } from '../../stubs';
describe('Image List Row', () => {
let wrapper;
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_spec.js
index 61c362f4d78..d7dd825ca3e 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlKeysetPagination } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Component from '~/registry/explorer/components/list_page/image_list.vue';
import ImageListRow from '~/registry/explorer/components/list_page/image_list_row.vue';
diff --git a/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js b/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js
index 3a27cf1923c..111aa45f231 100644
--- a/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/project_empty_state_spec.js
@@ -1,9 +1,9 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
-import { GlEmptyState } from '../../stubs';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import projectEmptyState from '~/registry/explorer/components/list_page/project_empty_state.vue';
import { dockerCommands } from '../../mock_data';
+import { GlEmptyState } from '../../stubs';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js
index 58439c185e3..07256d2bbf5 100644
--- a/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js
@@ -1,13 +1,13 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Component from '~/registry/explorer/components/list_page/registry_header.vue';
-import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import {
CONTAINER_REGISTRY_TITLE,
LIST_INTRO_TEXT,
EXPIRATION_POLICY_DISABLED_MESSAGE,
EXPIRATION_POLICY_DISABLED_TEXT,
} from '~/registry/explorer/constants';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
jest.mock('~/lib/utils/datetime_utility', () => ({
approximateDuration: jest.fn(),
diff --git a/spec/frontend/registry/explorer/mock_data.js b/spec/frontend/registry/explorer/mock_data.js
index b0fc009872c..f4453912db4 100644
--- a/spec/frontend/registry/explorer/mock_data.js
+++ b/spec/frontend/registry/explorer/mock_data.js
@@ -235,3 +235,9 @@ export const graphQLProjectImageRepositoriesDetailsMock = {
},
},
};
+
+export const graphQLEmptyImageDetailsMock = {
+ data: {
+ containerRepository: null,
+ },
+};
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index 1746a6a63b6..65c58bf9874 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -1,27 +1,35 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlKeysetPagination } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import Tracking from '~/tracking';
-import component from '~/registry/explorer/pages/details.vue';
+import axios from '~/lib/utils/axios_utils';
+import DeleteImage from '~/registry/explorer/components/delete_image.vue';
import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue';
-import PartialCleanupAlert from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue';
import DetailsHeader from '~/registry/explorer/components/details_page/details_header.vue';
-import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
+import EmptyTagsState from '~/registry/explorer/components/details_page/empty_state.vue';
+import PartialCleanupAlert from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue';
+import StatusAlert from '~/registry/explorer/components/details_page/status_alert.vue';
import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
-import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue';
+import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
-import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
+import {
+ UNFINISHED_STATUS,
+ DELETE_SCHEDULED,
+ ALERT_DANGER_IMAGE,
+} from '~/registry/explorer/constants';
import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql';
+import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
-import { UNFINISHED_STATUS } from '~/registry/explorer/constants/index';
+import component from '~/registry/explorer/pages/details.vue';
+import Tracking from '~/tracking';
import {
graphQLImageDetailsMock,
graphQLImageDetailsEmptyTagsMock,
graphQLDeleteImageRepositoryTagsMock,
containerRepositoryMock,
+ graphQLEmptyImageDetailsMock,
tagsMock,
tagsPageInfo,
} from '../mock_data';
@@ -39,8 +47,10 @@ describe('Details Page', () => {
const findTagsList = () => wrapper.find(TagsList);
const findDeleteAlert = () => wrapper.find(DeleteAlert);
const findDetailsHeader = () => wrapper.find(DetailsHeader);
- const findEmptyTagsState = () => wrapper.find(EmptyTagsState);
+ const findEmptyState = () => wrapper.find(EmptyTagsState);
const findPartialCleanupAlert = () => wrapper.find(PartialCleanupAlert);
+ const findStatusAlert = () => wrapper.find(StatusAlert);
+ const findDeleteImage = () => wrapper.find(DeleteImage);
const routeId = 1;
@@ -86,6 +96,7 @@ describe('Details Page', () => {
apolloProvider,
stubs: {
DeleteModal,
+ DeleteImage,
},
mocks: {
$route: {
@@ -133,6 +144,27 @@ describe('Details Page', () => {
});
});
+ describe('when the image does not exist', () => {
+ it('does not show the default ui', async () => {
+ mountComponent({ resolver: jest.fn().mockResolvedValue(graphQLEmptyImageDetailsMock) });
+
+ await waitForApolloRequestRender();
+
+ expect(findTagsLoader().exists()).toBe(false);
+ expect(findDetailsHeader().exists()).toBe(false);
+ expect(findTagsList().exists()).toBe(false);
+ expect(findPagination().exists()).toBe(false);
+ });
+
+ it('shows an empty state message', async () => {
+ mountComponent({ resolver: jest.fn().mockResolvedValue(graphQLEmptyImageDetailsMock) });
+
+ await waitForApolloRequestRender();
+
+ expect(findEmptyState().exists()).toBe(true);
+ });
+ });
+
describe('when the list of tags is empty', () => {
const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsEmptyTagsMock);
@@ -141,7 +173,7 @@ describe('Details Page', () => {
await waitForApolloRequestRender();
- expect(findEmptyTagsState().exists()).toBe(true);
+ expect(findEmptyState().exists()).toBe(true);
});
it('does not show the loader', async () => {
@@ -401,6 +433,9 @@ describe('Details Page', () => {
const config = {
runCleanupPoliciesHelpPagePath: 'foo',
cleanupPoliciesHelpPagePath: 'bar',
+ userCalloutsPath: 'call_out_path',
+ userCalloutId: 'call_out_id',
+ showUnfinishedTagCleanupCallout: true,
};
describe(`when expirationPolicyCleanupStatus is ${UNFINISHED_STATUS}`, () => {
@@ -413,8 +448,9 @@ describe('Details Page', () => {
}),
);
});
+
it('exists', async () => {
- mountComponent({ resolver });
+ mountComponent({ resolver, config });
await waitForApolloRequestRender();
@@ -426,11 +462,16 @@ describe('Details Page', () => {
await waitForApolloRequestRender();
- expect(findPartialCleanupAlert().props()).toEqual({ ...config });
+ expect(findPartialCleanupAlert().props()).toEqual({
+ runCleanupPoliciesHelpPagePath: config.runCleanupPoliciesHelpPagePath,
+ cleanupPoliciesHelpPagePath: config.cleanupPoliciesHelpPagePath,
+ });
});
it('dismiss hides the component', async () => {
- mountComponent({ resolver });
+ jest.spyOn(axios, 'post').mockReturnValue();
+
+ mountComponent({ resolver, config });
await waitForApolloRequestRender();
@@ -440,13 +481,25 @@ describe('Details Page', () => {
await wrapper.vm.$nextTick();
+ expect(axios.post).toHaveBeenCalledWith(config.userCalloutsPath, {
+ feature_name: config.userCalloutId,
+ });
+ expect(findPartialCleanupAlert().exists()).toBe(false);
+ });
+
+ it('is hidden if the callout is dismissed', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
expect(findPartialCleanupAlert().exists()).toBe(false);
});
});
describe(`when expirationPolicyCleanupStatus is not ${UNFINISHED_STATUS}`, () => {
it('the component is hidden', async () => {
- mountComponent();
+ mountComponent({ config });
+
await waitForApolloRequestRender();
expect(findPartialCleanupAlert().exists()).toBe(false);
@@ -463,4 +516,83 @@ describe('Details Page', () => {
expect(breadCrumbState.updateName).toHaveBeenCalledWith(containerRepositoryMock.name);
});
});
+
+ describe('when the image has a status different from null', () => {
+ const resolver = jest
+ .fn()
+ .mockResolvedValue(graphQLImageDetailsMock({ status: DELETE_SCHEDULED }));
+ it('disables all the actions', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ expect(findDetailsHeader().props('disabled')).toBe(true);
+ expect(findTagsList().props('disabled')).toBe(true);
+ });
+
+ it('shows a status alert', async () => {
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ expect(findStatusAlert().exists()).toBe(true);
+ expect(findStatusAlert().props()).toMatchObject({
+ status: DELETE_SCHEDULED,
+ });
+ });
+ });
+
+ describe('delete the image', () => {
+ const mountComponentAndDeleteImage = async () => {
+ mountComponent();
+
+ await waitForApolloRequestRender();
+ findDetailsHeader().vm.$emit('delete');
+
+ await wrapper.vm.$nextTick();
+ };
+
+ it('on delete event it deletes the image', async () => {
+ await mountComponentAndDeleteImage();
+
+ findDeleteModal().vm.$emit('confirmDelete');
+
+ expect(findDeleteImage().emitted('start')).toEqual([[]]);
+ });
+
+ it('binds the correct props to the modal', async () => {
+ await mountComponentAndDeleteImage();
+
+ expect(findDeleteModal().props()).toMatchObject({
+ itemsToBeDeleted: [{ path: 'gitlab-org/gitlab-test/rails-12009' }],
+ deleteImage: true,
+ });
+ });
+
+ it('binds correctly to delete-image start and end events', async () => {
+ mountComponent();
+
+ findDeleteImage().vm.$emit('start');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findTagsLoader().exists()).toBe(true);
+
+ findDeleteImage().vm.$emit('end');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findTagsLoader().exists()).toBe(false);
+ });
+
+ it('binds correctly to delete-image error event', async () => {
+ mountComponent();
+
+ findDeleteImage().vm.$emit('error');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findDeleteAlert().props('deleteAlertType')).toBe(ALERT_DANGER_IMAGE);
+ });
+ });
});
diff --git a/spec/frontend/registry/explorer/pages/list_spec.js b/spec/frontend/registry/explorer/pages/list_spec.js
index c4556934934..f7f207cc183 100644
--- a/spec/frontend/registry/explorer/pages/list_spec.js
+++ b/spec/frontend/registry/explorer/pages/list_spec.js
@@ -1,33 +1,32 @@
+import { GlSkeletonLoader, GlSprintf, GlAlert } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
-import { GlSkeletonLoader, GlSprintf, GlAlert, GlSearchBoxByClick } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
-import Tracking from '~/tracking';
-import component from '~/registry/explorer/pages/list.vue';
+import DeleteImage from '~/registry/explorer/components/delete_image.vue';
import CliCommands from '~/registry/explorer/components/list_page/cli_commands.vue';
import GroupEmptyState from '~/registry/explorer/components/list_page/group_empty_state.vue';
+import ImageList from '~/registry/explorer/components/list_page/image_list.vue';
import ProjectEmptyState from '~/registry/explorer/components/list_page/project_empty_state.vue';
import RegistryHeader from '~/registry/explorer/components/list_page/registry_header.vue';
-import ImageList from '~/registry/explorer/components/list_page/image_list.vue';
-import TitleArea from '~/vue_shared/components/registry/title_area.vue';
-
import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
- IMAGE_REPOSITORY_LIST_LABEL,
- SEARCH_PLACEHOLDER_TEXT,
+ SORT_FIELDS,
} from '~/registry/explorer/constants';
-
-import getContainerRepositoriesDetails from '~/registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
import deleteContainerRepositoryMutation from '~/registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
+import getContainerRepositoriesDetails from '~/registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
+import component from '~/registry/explorer/pages/list.vue';
+import Tracking from '~/tracking';
+import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { $toast } from '../../shared/mocks';
import {
graphQLImageListMock,
graphQLImageDeleteMock,
deletedContainerRepository,
- graphQLImageDeleteMockError,
graphQLEmptyImageListMock,
graphQLEmptyGroupImageListMock,
pageInfo,
@@ -35,7 +34,6 @@ import {
dockerCommands,
} from '../mock_data';
import { GlModal, GlEmptyState } from '../stubs';
-import { $toast } from '../../shared/mocks';
const localVue = createLocalVue();
@@ -55,9 +53,9 @@ describe('List Page', () => {
const findDeleteAlert = () => wrapper.find(GlAlert);
const findImageList = () => wrapper.find(ImageList);
- const findListHeader = () => wrapper.find('[data-testid="listHeader"]');
- const findSearchBox = () => wrapper.find(GlSearchBoxByClick);
+ const findRegistrySearch = () => wrapper.find(RegistrySearch);
const findEmptySearchMessage = () => wrapper.find('[data-testid="emptySearch"]');
+ const findDeleteImage = () => wrapper.find(DeleteImage);
const waitForApolloRequestRender = async () => {
jest.runOnlyPendingTimers();
@@ -91,6 +89,7 @@ describe('List Page', () => {
GlSprintf,
RegistryHeader,
TitleArea,
+ DeleteImage,
},
mocks: {
$toast,
@@ -227,14 +226,6 @@ describe('List Page', () => {
expect(findCliCommands().exists()).toBe(false);
});
-
- it('list header is not visible', async () => {
- mountComponent({ resolver, config });
-
- await waitForApolloRequestRender();
-
- expect(findListHeader().exists()).toBe(false);
- });
});
});
@@ -256,16 +247,6 @@ describe('List Page', () => {
expect(findImageList().exists()).toBe(true);
});
- it('list header is visible', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
-
- const header = findListHeader();
- expect(header.exists()).toBe(true);
- expect(header.text()).toBe(IMAGE_REPOSITORY_LIST_LABEL);
- });
-
describe('additional metadata', () => {
it('is called on component load', async () => {
const detailsResolver = jest
@@ -300,23 +281,22 @@ describe('List Page', () => {
});
describe('delete image', () => {
- const deleteImage = async () => {
- await wrapper.vm.$nextTick();
+ const selectImageForDeletion = async () => {
+ await waitForApolloRequestRender();
findImageList().vm.$emit('delete', deletedContainerRepository);
- findDeleteModal().vm.$emit('ok');
-
- await waitForApolloRequestRender();
};
it('should call deleteItem when confirming deletion', async () => {
const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock);
mountComponent({ mutationResolver });
- await deleteImage();
+ await selectImageForDeletion();
+
+ findDeleteModal().vm.$emit('primary');
+ await waitForApolloRequestRender();
expect(wrapper.vm.itemToDelete).toEqual(deletedContainerRepository);
- expect(mutationResolver).toHaveBeenCalledWith({ id: deletedContainerRepository.id });
const updatedImage = findImageList()
.props('images')
@@ -326,10 +306,12 @@ describe('List Page', () => {
});
it('should show a success alert when delete request is successful', async () => {
- const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock);
- mountComponent({ mutationResolver });
+ mountComponent();
+
+ await selectImageForDeletion();
- await deleteImage();
+ findDeleteImage().vm.$emit('success');
+ await wrapper.vm.$nextTick();
const alert = findDeleteAlert();
expect(alert.exists()).toBe(true);
@@ -340,23 +322,12 @@ describe('List Page', () => {
describe('when delete request fails it shows an alert', () => {
it('user recoverable error', async () => {
- const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMockError);
- mountComponent({ mutationResolver });
-
- await deleteImage();
-
- const alert = findDeleteAlert();
- expect(alert.exists()).toBe(true);
- expect(alert.text().replace(/\s\s+/gm, ' ')).toBe(
- DELETE_IMAGE_ERROR_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path),
- );
- });
+ mountComponent();
- it('network error', async () => {
- const mutationResolver = jest.fn().mockRejectedValue();
- mountComponent({ mutationResolver });
+ await selectImageForDeletion();
- await deleteImage();
+ findDeleteImage().vm.$emit('error');
+ await wrapper.vm.$nextTick();
const alert = findDeleteAlert();
expect(alert.exists()).toBe(true);
@@ -368,10 +339,15 @@ describe('List Page', () => {
});
});
- describe('search', () => {
+ describe('search and sorting', () => {
const doSearch = async () => {
await waitForApolloRequestRender();
- findSearchBox().vm.$emit('submit', 'centos6');
+ findRegistrySearch().vm.$emit('filter:changed', [
+ { type: 'filtered-search-term', value: { data: 'centos6' } },
+ ]);
+
+ findRegistrySearch().vm.$emit('filter:submit');
+
await wrapper.vm.$nextTick();
};
@@ -380,9 +356,26 @@ describe('List Page', () => {
await waitForApolloRequestRender();
- const searchBox = findSearchBox();
- expect(searchBox.exists()).toBe(true);
- expect(searchBox.attributes('placeholder')).toBe(SEARCH_PLACEHOLDER_TEXT);
+ const registrySearch = findRegistrySearch();
+ expect(registrySearch.exists()).toBe(true);
+ expect(registrySearch.props()).toMatchObject({
+ filter: [],
+ sorting: { orderBy: 'UPDATED', sort: 'desc' },
+ sortableFields: SORT_FIELDS,
+ tokens: [],
+ });
+ });
+
+ it('performs sorting', async () => {
+ const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
+ mountComponent({ resolver });
+
+ await waitForApolloRequestRender();
+
+ findRegistrySearch().vm.$emit('sorting:changed', { sort: 'asc' });
+ await wrapper.vm.$nextTick();
+
+ expect(resolver).toHaveBeenCalledWith(expect.objectContaining({ sort: 'UPDATED_DESC' }));
});
it('performs a search', async () => {
@@ -499,9 +492,8 @@ describe('List Page', () => {
testTrackingCall('cancel_delete');
});
- it('send an event when confirm is clicked on modal', () => {
- const deleteModal = findDeleteModal();
- deleteModal.vm.$emit('ok');
+ it('send an event when the deletion starts', () => {
+ findDeleteImage().vm.$emit('start');
testTrackingCall('confirm_delete');
});
});
diff --git a/spec/frontend/registry/settings/components/expiration_input_spec.js b/spec/frontend/registry/settings/components/expiration_input_spec.js
index 383158067dc..b91599a2789 100644
--- a/spec/frontend/registry/settings/components/expiration_input_spec.js
+++ b/spec/frontend/registry/settings/components/expiration_input_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlSprintf, GlFormInput, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_input.vue';
import { NAME_REGEX_LENGTH } from '~/registry/settings/constants';
diff --git a/spec/frontend/registry/settings/components/expiration_run_text_spec.js b/spec/frontend/registry/settings/components/expiration_run_text_spec.js
index d74ee2d1c18..753bb10ad08 100644
--- a/spec/frontend/registry/settings/components/expiration_run_text_spec.js
+++ b/spec/frontend/registry/settings/components/expiration_run_text_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_run_text.vue';
import { NEXT_CLEANUP_LABEL, NOT_SCHEDULED_POLICY_TEXT } from '~/registry/settings/constants';
diff --git a/spec/frontend/registry/settings/components/expiration_toggle_spec.js b/spec/frontend/registry/settings/components/expiration_toggle_spec.js
index ce016e852ee..961bdfdf2c5 100644
--- a/spec/frontend/registry/settings/components/expiration_toggle_spec.js
+++ b/spec/frontend/registry/settings/components/expiration_toggle_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlToggle, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_toggle.vue';
import {
diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
index 32d4f16221d..fd53efa884f 100644
--- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
@@ -1,15 +1,15 @@
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
-import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import component from '~/registry/settings/components/registry_settings_app.vue';
-import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
import SettingsForm from '~/registry/settings/components/settings_form.vue';
import {
FETCH_SETTINGS_ERROR_MESSAGE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
} from '~/registry/settings/constants';
+import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
import {
expirationPolicyPayload,
diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js
index 626f4fcc9f5..7527910ad59 100644
--- a/spec/frontend/registry/settings/components/settings_form_spec.js
+++ b/spec/frontend/registry/settings/components/settings_form_spec.js
@@ -2,14 +2,14 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import Tracking from '~/tracking';
import component from '~/registry/settings/components/settings_form.vue';
-import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql';
-import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '~/registry/settings/constants';
+import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql';
+import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
+import Tracking from '~/tracking';
import { GlCard, GlLoadingIcon } from '../../shared/stubs';
import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data';
diff --git a/spec/frontend/registry/settings/graphql/cache_updated_spec.js b/spec/frontend/registry/settings/graphql/cache_updated_spec.js
index d88a5576f26..73655b6917b 100644
--- a/spec/frontend/registry/settings/graphql/cache_updated_spec.js
+++ b/spec/frontend/registry/settings/graphql/cache_updated_spec.js
@@ -1,5 +1,5 @@
-import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
+import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
describe('Registry settings cache update', () => {
let client;
diff --git a/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js b/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js
index c084410c65b..f306fdef624 100644
--- a/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js
+++ b/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js
@@ -1,9 +1,9 @@
import { mount, createLocalVue } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import RelatedMergeRequests from '~/related_merge_requests/components/related_merge_requests.vue';
import createStore from '~/related_merge_requests/store/index';
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
const FIXTURE_PATH = 'issues/related_merge_requests.json';
const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests';
diff --git a/spec/frontend/related_merge_requests/store/actions_spec.js b/spec/frontend/related_merge_requests/store/actions_spec.js
index a4257d67176..a14096388e6 100644
--- a/spec/frontend/related_merge_requests/store/actions_spec.js
+++ b/spec/frontend/related_merge_requests/store/actions_spec.js
@@ -2,8 +2,8 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import * as types from '~/related_merge_requests/store/mutation_types';
import * as actions from '~/related_merge_requests/store/actions';
+import * as types from '~/related_merge_requests/store/mutation_types';
jest.mock('~/flash');
diff --git a/spec/frontend/related_merge_requests/store/mutations_spec.js b/spec/frontend/related_merge_requests/store/mutations_spec.js
index 21b6e26376b..436c7dca6ce 100644
--- a/spec/frontend/related_merge_requests/store/mutations_spec.js
+++ b/spec/frontend/related_merge_requests/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/related_merge_requests/store/mutations';
import * as types from '~/related_merge_requests/store/mutation_types';
+import mutations from '~/related_merge_requests/store/mutations';
describe('RelatedMergeRequests Store Mutations', () => {
describe('SET_INITIAL_STATE', () => {
diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js
index 828d1b46a80..1e55ab8f9e4 100644
--- a/spec/frontend/releases/components/app_edit_new_spec.js
+++ b/spec/frontend/releases/components/app_edit_new_spec.js
@@ -1,13 +1,13 @@
-import Vuex from 'vuex';
import { mount } from '@vue/test-utils';
-import { merge } from 'lodash';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { merge } from 'lodash';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
-import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue';
import * as commonUtils from '~/lib/utils/common_utils';
-import { BACK_URL_PARAM } from '~/releases/constants';
+import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue';
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
+import { BACK_URL_PARAM } from '~/releases/constants';
const originalRelease = getJSONFixture('api/releases/release.json');
const originalMilestones = originalRelease.milestones;
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js
index 1481dd30fd4..2b5270e29d6 100644
--- a/spec/frontend/releases/components/app_index_spec.js
+++ b/spec/frontend/releases/components/app_index_spec.js
@@ -1,15 +1,15 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { range as rge } from 'lodash';
import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import waitForPromises from 'helpers/wait_for_promises';
import { getJSONFixture } from 'helpers/fixtures';
+import waitForPromises from 'helpers/wait_for_promises';
+import api from '~/api';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ReleasesApp from '~/releases/components/app_index.vue';
+import ReleasesPagination from '~/releases/components/releases_pagination.vue';
import createStore from '~/releases/stores';
import createListModule from '~/releases/stores/modules/list';
-import api from '~/api';
import { pageInfoHeadersWithoutPagination, pageInfoHeadersWithPagination } from '../mock_data';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import ReleasesPagination from '~/releases/components/releases_pagination.vue';
jest.mock('~/lib/utils/common_utils', () => ({
...jest.requireActual('~/lib/utils/common_utils'),
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index b1f94ca9859..5caea395f0a 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ReleaseShowApp from '~/releases/components/app_show.vue';
-import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
import ReleaseBlock from '~/releases/components/release_block.vue';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
const originalRelease = getJSONFixture('api/releases/release.json');
diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js
index 2b2ad0150ab..bbaa4e9dc94 100644
--- a/spec/frontend/releases/components/asset_links_form_spec.js
+++ b/spec/frontend/releases/components/asset_links_form_spec.js
@@ -1,9 +1,9 @@
-import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
-import AssetLinksForm from '~/releases/components/asset_links_form.vue';
import * as commonUtils from '~/lib/utils/common_utils';
import { ENTER_KEY } from '~/lib/utils/keys';
+import AssetLinksForm from '~/releases/components/asset_links_form.vue';
import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
const localVue = createLocalVue();
diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js
index b8c78f90fc2..50b6d1c4707 100644
--- a/spec/frontend/releases/components/evidence_block_spec.js
+++ b/spec/frontend/releases/components/evidence_block_spec.js
@@ -1,10 +1,10 @@
-import { mount } from '@vue/test-utils';
import { GlLink, GlIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility';
import EvidenceBlock from '~/releases/components/evidence_block.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
const originalRelease = getJSONFixture('api/releases/release.json');
diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js
index 77bd35f94aa..3b9b16fa890 100644
--- a/spec/frontend/releases/components/release_block_assets_spec.js
+++ b/spec/frontend/releases/components/release_block_assets_spec.js
@@ -1,7 +1,7 @@
-import { mount } from '@vue/test-utils';
import { GlCollapse } from '@gitlab/ui';
-import { trimText } from 'helpers/text_helper';
+import { mount } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
+import { trimText } from 'helpers/text_helper';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue';
import { ASSET_LINK_TYPE } from '~/releases/constants';
diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js
index f1c0c24f8ca..e9fa22b4ec7 100644
--- a/spec/frontend/releases/components/release_block_footer_spec.js
+++ b/spec/frontend/releases/components/release_block_footer_spec.js
@@ -1,26 +1,16 @@
-import { mount } from '@vue/test-utils';
import { GlLink, GlIcon } from '@gitlab/ui';
-import { trimText } from 'helpers/text_helper';
-import { getJSONFixture } from 'helpers/fixtures';
+import { mount } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
-import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
+import { getJSONFixture } from 'helpers/fixtures';
+import { trimText } from 'helpers/text_helper';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
const originalRelease = getJSONFixture('api/releases/release.json');
-const mockFutureDate = new Date(9999, 0, 0).toISOString();
-let mockIsFutureRelease = false;
-
-jest.mock('~/vue_shared/mixins/timeago', () => ({
- methods: {
- timeFormatted() {
- return mockIsFutureRelease ? 'in 1 month' : '7 fortnights ago';
- },
- tooltipTitle() {
- return 'February 30, 2401';
- },
- },
-}));
+// TODO: Encapsulate date helpers https://gitlab.com/gitlab-org/gitlab/-/issues/320883
+const MONTHS_IN_MS = 1000 * 60 * 60 * 24 * 31;
+const mockFutureDate = new Date(new Date().getTime() + MONTHS_IN_MS).toISOString();
describe('Release block footer', () => {
let wrapper;
@@ -44,7 +34,6 @@ describe('Release block footer', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- mockIsFutureRelease = false;
});
const commitInfoSection = () => wrapper.find('.js-commit-info');
@@ -88,7 +77,7 @@ describe('Release block footer', () => {
it('renders the author and creation time info', () => {
expect(trimText(authorDateInfoSection().text())).toBe(
- `Created 7 fortnights ago by ${release.author.username}`,
+ `Created 1 year ago by ${release.author.username}`,
);
});
@@ -100,7 +89,6 @@ describe('Release block footer', () => {
describe('renders the author and creation time info with future release date', () => {
beforeEach(() => {
- mockIsFutureRelease = true;
factory({ releasedAt: mockFutureDate });
});
@@ -113,7 +101,6 @@ describe('Release block footer', () => {
describe('when the release date is in the future', () => {
beforeEach(() => {
- mockIsFutureRelease = true;
factory({ releasedAt: mockFutureDate });
});
@@ -177,13 +164,12 @@ describe('Release block footer', () => {
beforeEach(() => factory({ author: undefined }));
it('renders the release date without the author name', () => {
- expect(trimText(authorDateInfoSection().text())).toBe(`Created 7 fortnights ago`);
+ expect(trimText(authorDateInfoSection().text())).toBe(`Created 1 year ago`);
});
});
describe('future release without any author info', () => {
beforeEach(() => {
- mockIsFutureRelease = true;
factory({ author: undefined, releasedAt: mockFutureDate });
});
diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js
index f2159871395..0f6657090e6 100644
--- a/spec/frontend/releases/components/release_block_header_spec.js
+++ b/spec/frontend/releases/components/release_block_header_spec.js
@@ -1,9 +1,9 @@
+import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { merge } from 'lodash';
-import { GlLink } from '@gitlab/ui';
import { getJSONFixture } from 'helpers/fixtures';
-import ReleaseBlockHeader from '~/releases/components/release_block_header.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import ReleaseBlockHeader from '~/releases/components/release_block_header.vue';
import { BACK_URL_PARAM } from '~/releases/constants';
const originalRelease = getJSONFixture('api/releases/release.json');
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 4eb15e9da12..47fe10af946 100644
--- a/spec/frontend/releases/components/release_block_milestone_info_spec.js
+++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js
@@ -1,10 +1,10 @@
-import { mount } from '@vue/test-utils';
import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui';
-import { trimText } from 'helpers/text_helper';
+import { mount } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
+import { trimText } from 'helpers/text_helper';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue';
import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/constants';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
const { milestones: originalMilestones } = getJSONFixture('api/releases/release.json');
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index 633c6690529..1ca441f7a5a 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -1,13 +1,13 @@
-import $ from 'jquery';
import { mount } from '@vue/test-utils';
+import $ from 'jquery';
import { getJSONFixture } from 'helpers/fixtures';
+import * as commonUtils from '~/lib/utils/common_utils';
+import * as urlUtility from '~/lib/utils/url_utility';
import EvidenceBlock from '~/releases/components/evidence_block.vue';
import ReleaseBlock from '~/releases/components/release_block.vue';
import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
-import * as commonUtils from '~/lib/utils/common_utils';
import { BACK_URL_PARAM } from '~/releases/constants';
-import * as urlUtility from '~/lib/utils/url_utility';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
const originalRelease = getJSONFixture('api/releases/release.json');
diff --git a/spec/frontend/releases/components/release_skeleton_loader_spec.js b/spec/frontend/releases/components/release_skeleton_loader_spec.js
index 7fbf864568a..7f81081ff6c 100644
--- a/spec/frontend/releases/components/release_skeleton_loader_spec.js
+++ b/spec/frontend/releases/components/release_skeleton_loader_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlSkeletonLoader } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
describe('release_skeleton_loader.vue', () => {
diff --git a/spec/frontend/releases/components/releases_pagination_graphql_spec.js b/spec/frontend/releases/components/releases_pagination_graphql_spec.js
index cee5e72e1c0..de80d82e93c 100644
--- a/spec/frontend/releases/components/releases_pagination_graphql_spec.js
+++ b/spec/frontend/releases/components/releases_pagination_graphql_spec.js
@@ -1,9 +1,9 @@
-import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { historyPushState } from '~/lib/utils/common_utils';
+import ReleasesPaginationGraphql from '~/releases/components/releases_pagination_graphql.vue';
import createStore from '~/releases/stores';
import createListModule from '~/releases/stores/modules/list';
-import ReleasesPaginationGraphql from '~/releases/components/releases_pagination_graphql.vue';
-import { historyPushState } from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/common_utils', () => ({
...jest.requireActual('~/lib/utils/common_utils'),
diff --git a/spec/frontend/releases/components/releases_pagination_rest_spec.js b/spec/frontend/releases/components/releases_pagination_rest_spec.js
index b015792c96b..6f2690f5322 100644
--- a/spec/frontend/releases/components/releases_pagination_rest_spec.js
+++ b/spec/frontend/releases/components/releases_pagination_rest_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlPagination } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import * as commonUtils from '~/lib/utils/common_utils';
import ReleasesPaginationRest from '~/releases/components/releases_pagination_rest.vue';
import createStore from '~/releases/stores';
import createListModule from '~/releases/stores/modules/list';
-import * as commonUtils from '~/lib/utils/common_utils';
commonUtils.historyPushState = jest.fn();
diff --git a/spec/frontend/releases/components/releases_sort_spec.js b/spec/frontend/releases/components/releases_sort_spec.js
index c089ee3cc38..f17c6678592 100644
--- a/spec/frontend/releases/components/releases_sort_spec.js
+++ b/spec/frontend/releases/components/releases_sort_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import ReleasesSort from '~/releases/components/releases_sort.vue';
import createStore from '~/releases/stores';
import createListModule from '~/releases/stores/modules/list';
diff --git a/spec/frontend/releases/components/tag_field_exsting_spec.js b/spec/frontend/releases/components/tag_field_exsting_spec.js
index d4110b57776..cef7a0272a6 100644
--- a/spec/frontend/releases/components/tag_field_exsting_spec.js
+++ b/spec/frontend/releases/components/tag_field_exsting_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
import { GlFormInput } from '@gitlab/ui';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import TagFieldExisting from '~/releases/components/tag_field_existing.vue';
import createStore from '~/releases/stores';
import createDetailModule from '~/releases/stores/modules/detail';
diff --git a/spec/frontend/releases/components/tag_field_new_spec.js b/spec/frontend/releases/components/tag_field_new_spec.js
index eba0e286b27..d87718138b8 100644
--- a/spec/frontend/releases/components/tag_field_new_spec.js
+++ b/spec/frontend/releases/components/tag_field_new_spec.js
@@ -1,9 +1,9 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+import RefSelector from '~/ref/components/ref_selector.vue';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import createStore from '~/releases/stores';
import createDetailModule from '~/releases/stores/modules/detail';
-import RefSelector from '~/ref/components/ref_selector.vue';
const TEST_TAG_NAME = 'test-tag-name';
const TEST_PROJECT_ID = '1234';
diff --git a/spec/frontend/releases/components/tag_field_spec.js b/spec/frontend/releases/components/tag_field_spec.js
index c7909a2369b..2cf5944f9e6 100644
--- a/spec/frontend/releases/components/tag_field_spec.js
+++ b/spec/frontend/releases/components/tag_field_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import TagField from '~/releases/components/tag_field.vue';
-import TagFieldNew from '~/releases/components/tag_field_new.vue';
import TagFieldExisting from '~/releases/components/tag_field_existing.vue';
+import TagFieldNew from '~/releases/components/tag_field_new.vue';
import createStore from '~/releases/stores';
import createDetailModule from '~/releases/stores/modules/detail';
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js
index 396e7bd8745..9c125fbb87b 100644
--- a/spec/frontend/releases/stores/modules/detail/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js
@@ -1,17 +1,17 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
-import { getJSONFixture } from 'helpers/fixtures';
import { cloneDeep } from 'lodash';
-import * as actions from '~/releases/stores/modules/detail/actions';
-import * as types from '~/releases/stores/modules/detail/mutation_types';
-import createState from '~/releases/stores/modules/detail/state';
+import { getJSONFixture } from 'helpers/fixtures';
+import testAction from 'helpers/vuex_action_helper';
+import api from '~/api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { redirectTo } from '~/lib/utils/url_utility';
-import api from '~/api';
import httpStatus from '~/lib/utils/http_status';
+import { redirectTo } from '~/lib/utils/url_utility';
import { ASSET_LINK_TYPE } from '~/releases/constants';
+import * as actions from '~/releases/stores/modules/detail/actions';
+import * as types from '~/releases/stores/modules/detail/mutation_types';
+import createState from '~/releases/stores/modules/detail/state';
import { releaseToApiJson, apiJsonToRelease } from '~/releases/util';
jest.mock('~/flash');
diff --git a/spec/frontend/releases/stores/modules/detail/mutations_spec.js b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
index 88eddc4019c..cdf26bfa834 100644
--- a/spec/frontend/releases/stores/modules/detail/mutations_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
@@ -1,9 +1,9 @@
import { getJSONFixture } from 'helpers/fixtures';
-import createState from '~/releases/stores/modules/detail/state';
-import mutations from '~/releases/stores/modules/detail/mutations';
-import * as types from '~/releases/stores/modules/detail/mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
+import * as types from '~/releases/stores/modules/detail/mutation_types';
+import mutations from '~/releases/stores/modules/detail/mutations';
+import createState from '~/releases/stores/modules/detail/state';
const originalRelease = getJSONFixture('api/releases/release.json');
diff --git a/spec/frontend/releases/stores/modules/list/actions_spec.js b/spec/frontend/releases/stores/modules/list/actions_spec.js
index 35551b77dc4..309f7387929 100644
--- a/spec/frontend/releases/stores/modules/list/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/list/actions_spec.js
@@ -1,6 +1,14 @@
import { cloneDeep } from 'lodash';
-import testAction from 'helpers/vuex_action_helper';
import { getJSONFixture } from 'helpers/fixtures';
+import testAction from 'helpers/vuex_action_helper';
+import api from '~/api';
+import {
+ normalizeHeaders,
+ parseIntPagination,
+ convertObjectPropsToCamelCase,
+} from '~/lib/utils/common_utils';
+import { PAGE_SIZE } from '~/releases/constants';
+import allReleasesQuery from '~/releases/queries/all_releases.query.graphql';
import {
fetchReleases,
fetchReleasesGraphQl,
@@ -8,18 +16,10 @@ import {
receiveReleasesError,
setSorting,
} from '~/releases/stores/modules/list/actions';
-import createState from '~/releases/stores/modules/list/state';
import * as types from '~/releases/stores/modules/list/mutation_types';
-import api from '~/api';
+import createState from '~/releases/stores/modules/list/state';
import { gqClient, convertAllReleasesGraphQLResponse } from '~/releases/util';
-import {
- normalizeHeaders,
- parseIntPagination,
- convertObjectPropsToCamelCase,
-} from '~/lib/utils/common_utils';
import { pageInfoHeadersWithoutPagination } from '../../../mock_data';
-import allReleasesQuery from '~/releases/queries/all_releases.query.graphql';
-import { PAGE_SIZE } from '~/releases/constants';
const originalRelease = getJSONFixture('api/releases/release.json');
const originalReleases = [originalRelease];
diff --git a/spec/frontend/releases/stores/modules/list/mutations_spec.js b/spec/frontend/releases/stores/modules/list/mutations_spec.js
index 78071573072..ea6a4ada16a 100644
--- a/spec/frontend/releases/stores/modules/list/mutations_spec.js
+++ b/spec/frontend/releases/stores/modules/list/mutations_spec.js
@@ -1,10 +1,10 @@
import { getJSONFixture } from 'helpers/fixtures';
-import createState from '~/releases/stores/modules/list/state';
-import mutations from '~/releases/stores/modules/list/mutations';
-import * as types from '~/releases/stores/modules/list/mutation_types';
import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { pageInfoHeadersWithoutPagination } from '../../../mock_data';
+import * as types from '~/releases/stores/modules/list/mutation_types';
+import mutations from '~/releases/stores/modules/list/mutations';
+import createState from '~/releases/stores/modules/list/state';
import { convertAllReleasesGraphQLResponse } from '~/releases/util';
+import { pageInfoHeadersWithoutPagination } from '../../../mock_data';
const originalRelease = getJSONFixture('api/releases/release.json');
const originalReleases = [originalRelease];
diff --git a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
index ccceb78f2d1..b716d54c9fc 100644
--- a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
+++ b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
@@ -1,7 +1,7 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import GroupedAccessibilityReportsApp from '~/reports/accessibility_report/grouped_accessibility_reports_app.vue';
import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue';
+import GroupedAccessibilityReportsApp from '~/reports/accessibility_report/grouped_accessibility_reports_app.vue';
import { getStoreConfig } from '~/reports/accessibility_report/store';
import { mockReport } from './mock_data';
diff --git a/spec/frontend/reports/accessibility_report/store/actions_spec.js b/spec/frontend/reports/accessibility_report/store/actions_spec.js
index 4e607fa5a76..46dbe1ff7a1 100644
--- a/spec/frontend/reports/accessibility_report/store/actions_spec.js
+++ b/spec/frontend/reports/accessibility_report/store/actions_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
import testAction from 'helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
+import createStore from '~/reports/accessibility_report/store';
import * as actions from '~/reports/accessibility_report/store/actions';
import * as types from '~/reports/accessibility_report/store/mutation_types';
-import createStore from '~/reports/accessibility_report/store';
import { mockReport } from '../mock_data';
describe('Accessibility Reports actions', () => {
diff --git a/spec/frontend/reports/accessibility_report/store/getters_spec.js b/spec/frontend/reports/accessibility_report/store/getters_spec.js
index d74c71cfa09..96344596003 100644
--- a/spec/frontend/reports/accessibility_report/store/getters_spec.js
+++ b/spec/frontend/reports/accessibility_report/store/getters_spec.js
@@ -1,5 +1,5 @@
-import * as getters from '~/reports/accessibility_report/store/getters';
import createStore from '~/reports/accessibility_report/store';
+import * as getters from '~/reports/accessibility_report/store/getters';
import { LOADING, ERROR, SUCCESS, STATUS_FAILED } from '~/reports/constants';
describe('Accessibility reports store getters', () => {
diff --git a/spec/frontend/reports/accessibility_report/store/mutations_spec.js b/spec/frontend/reports/accessibility_report/store/mutations_spec.js
index a4e9571b721..b336261d804 100644
--- a/spec/frontend/reports/accessibility_report/store/mutations_spec.js
+++ b/spec/frontend/reports/accessibility_report/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/reports/accessibility_report/store/mutations';
import createStore from '~/reports/accessibility_report/store';
+import mutations from '~/reports/accessibility_report/store/mutations';
describe('Accessibility Reports mutations', () => {
let localState;
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
index ecb657af6f1..f0b23bb7b58 100644
--- a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -1,7 +1,7 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
+import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
import { getStoreConfig } from '~/reports/codequality_report/store';
import { mockParsedHeadIssues, mockParsedBaseIssues } from './mock_data';
@@ -9,7 +9,6 @@ const localVue = createLocalVue();
localVue.use(Vuex);
describe('Grouped code quality reports app', () => {
- const Component = localVue.extend(GroupedCodequalityReportsApp);
let wrapper;
let mockStore;
@@ -22,7 +21,7 @@ describe('Grouped code quality reports app', () => {
};
const mountComponent = (props = {}) => {
- wrapper = mount(Component, {
+ wrapper = mount(GroupedCodequalityReportsApp, {
store: mockStore,
localVue,
propsData: {
@@ -135,7 +134,7 @@ describe('Grouped code quality reports app', () => {
});
it('renders error text', () => {
- expect(findWidget().text()).toEqual('Failed to load codeclimate report');
+ expect(findWidget().text()).toContain('Failed to load codeclimate report');
});
it('renders a help icon with more information', () => {
diff --git a/spec/frontend/reports/codequality_report/mock_data.js b/spec/frontend/reports/codequality_report/mock_data.js
index 9bd61527d3f..c5cecb34509 100644
--- a/spec/frontend/reports/codequality_report/mock_data.js
+++ b/spec/frontend/reports/codequality_report/mock_data.js
@@ -88,3 +88,53 @@ export const issueDiff = [
urlPath: 'headPath/lib/six.rb#L6',
},
];
+
+export const reportIssues = {
+ status: 'failed',
+ new_errors: [
+ {
+ description:
+ 'Method `long_if` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.',
+ severity: 'minor',
+ file_path: 'codequality.rb',
+ line: 5,
+ },
+ ],
+ resolved_errors: [
+ {
+ description: 'Insecure Dependency',
+ severity: 'major',
+ file_path: 'lib/six.rb',
+ line: 22,
+ },
+ ],
+ existing_errors: [],
+ summary: { total: 3, resolved: 0, errored: 3 },
+};
+
+export const parsedReportIssues = {
+ newIssues: [
+ {
+ description:
+ 'Method `long_if` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.',
+ file_path: 'codequality.rb',
+ line: 5,
+ name:
+ 'Method `long_if` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.',
+ path: 'codequality.rb',
+ severity: 'minor',
+ urlPath: 'null/codequality.rb#L5',
+ },
+ ],
+ resolvedIssues: [
+ {
+ description: 'Insecure Dependency',
+ file_path: 'lib/six.rb',
+ line: 22,
+ name: 'Insecure Dependency',
+ path: 'lib/six.rb',
+ severity: 'major',
+ urlPath: 'null/lib/six.rb#L22',
+ },
+ ],
+};
diff --git a/spec/frontend/reports/codequality_report/store/actions_spec.js b/spec/frontend/reports/codequality_report/store/actions_spec.js
index 321785cb85a..a2b256448ef 100644
--- a/spec/frontend/reports/codequality_report/store/actions_spec.js
+++ b/spec/frontend/reports/codequality_report/store/actions_spec.js
@@ -1,11 +1,18 @@
import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'spec/test_constants';
import testAction from 'helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
+import createStore from '~/reports/codequality_report/store';
import * as actions from '~/reports/codequality_report/store/actions';
import * as types from '~/reports/codequality_report/store/mutation_types';
-import createStore from '~/reports/codequality_report/store';
-import { headIssues, baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../mock_data';
+import {
+ headIssues,
+ baseIssues,
+ mockParsedHeadIssues,
+ mockParsedBaseIssues,
+ reportIssues,
+ parsedReportIssues,
+} from '../mock_data';
// mock codequality comparison worker
jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () =>
@@ -39,6 +46,7 @@ describe('Codequality Reports actions', () => {
headPath: 'headPath',
baseBlobPath: 'baseBlobPath',
headBlobPath: 'headBlobPath',
+ reportsPath: 'reportsPath',
helpPath: 'codequalityHelpPath',
};
@@ -55,68 +63,119 @@ describe('Codequality Reports actions', () => {
describe('fetchReports', () => {
let mock;
+ let diffFeatureFlagEnabled;
- beforeEach(() => {
- localState.headPath = `${TEST_HOST}/head.json`;
- localState.basePath = `${TEST_HOST}/base.json`;
- mock = new MockAdapter(axios);
- });
+ describe('with codequalityBackendComparison feature flag enabled', () => {
+ beforeEach(() => {
+ diffFeatureFlagEnabled = true;
+ localState.reportsPath = `${TEST_HOST}/codequality_reports.json`;
+ mock = new MockAdapter(axios);
+ });
- afterEach(() => {
- mock.restore();
- });
+ afterEach(() => {
+ mock.restore();
+ });
- describe('on success', () => {
- it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', (done) => {
- mock.onGet(`${TEST_HOST}/head.json`).reply(200, headIssues);
- mock.onGet(`${TEST_HOST}/base.json`).reply(200, baseIssues);
-
- testAction(
- actions.fetchReports,
- null,
- localState,
- [{ type: types.REQUEST_REPORTS }],
- [
- {
- payload: {
- newIssues: [mockParsedHeadIssues[0]],
- resolvedIssues: [mockParsedBaseIssues[0]],
+ describe('on success', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', (done) => {
+ mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(200, reportIssues);
+
+ testAction(
+ actions.fetchReports,
+ diffFeatureFlagEnabled,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [
+ {
+ payload: parsedReportIssues,
+ type: 'receiveReportsSuccess',
},
- type: 'receiveReportsSuccess',
- },
- ],
- done,
- );
+ ],
+ done,
+ );
+ });
});
- });
- describe('on error', () => {
- it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
- mock.onGet(`${TEST_HOST}/head.json`).reply(500);
-
- testAction(
- actions.fetchReports,
- null,
- localState,
- [{ type: types.REQUEST_REPORTS }],
- [{ type: 'receiveReportsError' }],
- done,
- );
+ describe('on error', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
+ mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(500);
+
+ testAction(
+ actions.fetchReports,
+ diffFeatureFlagEnabled,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError', payload: expect.any(Error) }],
+ done,
+ );
+ });
});
});
- describe('with no base path', () => {
- it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
- localState.basePath = null;
-
- testAction(
- actions.fetchReports,
- null,
- localState,
- [{ type: types.REQUEST_REPORTS }],
- [{ type: 'receiveReportsError' }],
- done,
- );
+ describe('with codequalityBackendComparison feature flag disabled', () => {
+ beforeEach(() => {
+ diffFeatureFlagEnabled = false;
+ localState.headPath = `${TEST_HOST}/head.json`;
+ localState.basePath = `${TEST_HOST}/base.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('on success', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', (done) => {
+ mock.onGet(`${TEST_HOST}/head.json`).reply(200, headIssues);
+ mock.onGet(`${TEST_HOST}/base.json`).reply(200, baseIssues);
+
+ testAction(
+ actions.fetchReports,
+ diffFeatureFlagEnabled,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [
+ {
+ payload: {
+ newIssues: [mockParsedHeadIssues[0]],
+ resolvedIssues: [mockParsedBaseIssues[0]],
+ },
+ type: 'receiveReportsSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('on error', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
+ mock.onGet(`${TEST_HOST}/head.json`).reply(500);
+
+ testAction(
+ actions.fetchReports,
+ diffFeatureFlagEnabled,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError', payload: expect.any(Error) }],
+ done,
+ );
+ });
+ });
+
+ describe('with no base path', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
+ localState.basePath = null;
+
+ testAction(
+ actions.fetchReports,
+ diffFeatureFlagEnabled,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError' }],
+ done,
+ );
+ });
});
});
});
@@ -142,7 +201,7 @@ describe('Codequality Reports actions', () => {
actions.receiveReportsError,
null,
localState,
- [{ type: types.RECEIVE_REPORTS_ERROR }],
+ [{ type: types.RECEIVE_REPORTS_ERROR, payload: null }],
[],
done,
);
diff --git a/spec/frontend/reports/codequality_report/store/getters_spec.js b/spec/frontend/reports/codequality_report/store/getters_spec.js
index a641e2fe74f..de025f814ef 100644
--- a/spec/frontend/reports/codequality_report/store/getters_spec.js
+++ b/spec/frontend/reports/codequality_report/store/getters_spec.js
@@ -1,5 +1,5 @@
-import * as getters from '~/reports/codequality_report/store/getters';
import createStore from '~/reports/codequality_report/store';
+import * as getters from '~/reports/codequality_report/store/getters';
import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
describe('Codequality reports store getters', () => {
diff --git a/spec/frontend/reports/codequality_report/store/mutations_spec.js b/spec/frontend/reports/codequality_report/store/mutations_spec.js
index 658abf3088c..05a16cd6f82 100644
--- a/spec/frontend/reports/codequality_report/store/mutations_spec.js
+++ b/spec/frontend/reports/codequality_report/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/reports/codequality_report/store/mutations';
import createStore from '~/reports/codequality_report/store';
+import mutations from '~/reports/codequality_report/store/mutations';
describe('Codequality Reports mutations', () => {
let localState;
@@ -55,6 +55,12 @@ describe('Codequality Reports mutations', () => {
expect(localState.hasError).toEqual(false);
});
+ it('clears statusReason', () => {
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, {});
+
+ expect(localState.statusReason).toEqual('');
+ });
+
it('sets newIssues and resolvedIssues from response data', () => {
const data = { newIssues: [{ id: 1 }], resolvedIssues: [{ id: 2 }] };
mutations.RECEIVE_REPORTS_SUCCESS(localState, data);
@@ -76,5 +82,13 @@ describe('Codequality Reports mutations', () => {
expect(localState.hasError).toEqual(true);
});
+
+ it('sets statusReason to string from error response data', () => {
+ const data = { status_reason: 'This merge request does not have codequality reports' };
+ const error = { response: { data } };
+ mutations.RECEIVE_REPORTS_ERROR(localState, error);
+
+ expect(localState.statusReason).toEqual(data.status_reason);
+ });
});
});
diff --git a/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
index 085d697672d..389e9b4a1f6 100644
--- a/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
+++ b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
@@ -2,7 +2,13 @@ import {
parseCodeclimateMetrics,
doCodeClimateComparison,
} from '~/reports/codequality_report/store/utils/codequality_comparison';
-import { baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../../mock_data';
+import {
+ baseIssues,
+ mockParsedHeadIssues,
+ mockParsedBaseIssues,
+ reportIssues,
+ parsedReportIssues,
+} from '../../mock_data';
jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () => {
let mockPostMessageCallback;
@@ -34,7 +40,7 @@ describe('Codequality report store utils', () => {
let result;
describe('parseCodeclimateMetrics', () => {
- it('should parse the received issues', () => {
+ it('should parse the issues from codeclimate artifacts', () => {
[result] = parseCodeclimateMetrics(baseIssues, 'path');
expect(result.name).toEqual(baseIssues[0].check_name);
@@ -42,6 +48,14 @@ describe('Codequality report store utils', () => {
expect(result.line).toEqual(baseIssues[0].location.lines.begin);
});
+ it('should parse the issues from backend codequality diff', () => {
+ [result] = parseCodeclimateMetrics(reportIssues.new_errors, 'path');
+
+ expect(result.name).toEqual(parsedReportIssues.newIssues[0].name);
+ expect(result.path).toEqual(parsedReportIssues.newIssues[0].path);
+ expect(result.line).toEqual(parsedReportIssues.newIssues[0].line);
+ });
+
describe('when an issue has no location or path', () => {
const issue = { description: 'Insecure Dependency' };
diff --git a/spec/frontend/reports/components/grouped_test_reports_app_spec.js b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
index 492192988fb..ed261ed12c0 100644
--- a/spec/frontend/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
@@ -5,11 +5,11 @@ import GroupedTestReportsApp from '~/reports/components/grouped_test_reports_app
import { getStoreConfig } from '~/reports/store';
import { failedReport } from '../mock_data/mock_data';
-import successTestReports from '../mock_data/no_failures_report.json';
+import mixedResultsTestReports from '../mock_data/new_and_fixed_failures_report.json';
+import newErrorsTestReports from '../mock_data/new_errors_report.json';
import newFailedTestReports from '../mock_data/new_failures_report.json';
+import successTestReports from '../mock_data/no_failures_report.json';
import recentFailuresTestReports from '../mock_data/recent_failures_report.json';
-import newErrorsTestReports from '../mock_data/new_errors_report.json';
-import mixedResultsTestReports from '../mock_data/new_and_fixed_failures_report.json';
import resolvedFailures from '../mock_data/resolved_failures.json';
const localVue = createLocalVue();
@@ -18,12 +18,11 @@ localVue.use(Vuex);
describe('Grouped test reports app', () => {
const endpoint = 'endpoint.json';
const pipelinePath = '/path/to/pipeline';
- const Component = localVue.extend(GroupedTestReportsApp);
let wrapper;
let mockStore;
const mountComponent = ({ props = { pipelinePath } } = {}) => {
- wrapper = mount(Component, {
+ wrapper = mount(GroupedTestReportsApp, {
store: mockStore,
localVue,
propsData: {
diff --git a/spec/frontend/reports/components/modal_spec.js b/spec/frontend/reports/components/modal_spec.js
index 39b84d1ee05..d47bb964e8a 100644
--- a/spec/frontend/reports/components/modal_spec.js
+++ b/spec/frontend/reports/components/modal_spec.js
@@ -2,9 +2,9 @@ import { GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import CodeBlock from '~/vue_shared/components/code_block.vue';
import ReportsModal from '~/reports/components/modal.vue';
import state from '~/reports/store/state';
+import CodeBlock from '~/vue_shared/components/code_block.vue';
const StubbedGlModal = { template: '<div><slot></slot></div>', name: 'GlModal', props: ['title'] };
diff --git a/spec/frontend/reports/components/report_item_spec.js b/spec/frontend/reports/components/report_item_spec.js
index 6aac07984e3..a7243c5377b 100644
--- a/spec/frontend/reports/components/report_item_spec.js
+++ b/spec/frontend/reports/components/report_item_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import { STATUS_SUCCESS } from '~/reports/constants';
-import ReportItem from '~/reports/components/report_item.vue';
-import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
import { componentNames } from '~/reports/components/issue_body';
+import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
+import ReportItem from '~/reports/components/report_item.vue';
+import { STATUS_SUCCESS } from '~/reports/constants';
describe('ReportItem', () => {
describe('showReportSectionStatusIcon', () => {
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index c3219b34057..c9bf3185f8f 100644
--- a/spec/frontend/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import reportSection from '~/reports/components/report_section.vue';
diff --git a/spec/frontend/reports/components/summary_row_spec.js b/spec/frontend/reports/components/summary_row_spec.js
index 85c68ed069b..bdd6de1e0be 100644
--- a/spec/frontend/reports/components/summary_row_spec.js
+++ b/spec/frontend/reports/components/summary_row_spec.js
@@ -32,7 +32,7 @@ describe('Summary row', () => {
it('renders provided summary', () => {
createComponent();
- expect(findSummary().text()).toEqual(props.summary);
+ expect(findSummary().text()).toContain(props.summary);
});
it('renders provided icon', () => {
@@ -48,7 +48,7 @@ describe('Summary row', () => {
createComponent({ slots: { summary: summarySlotContent } });
expect(wrapper.text()).not.toContain(props.summary);
- expect(findSummary().text()).toEqual(summarySlotContent);
+ expect(findSummary().text()).toContain(summarySlotContent);
});
});
});
diff --git a/spec/frontend/reports/components/test_issue_body_spec.js b/spec/frontend/reports/components/test_issue_body_spec.js
index c13a3599fef..2843620a18d 100644
--- a/spec/frontend/reports/components/test_issue_body_spec.js
+++ b/spec/frontend/reports/components/test_issue_body_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { trimText } from 'helpers/text_helper';
+import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import component from '~/reports/components/test_issue_body.vue';
import createStore from '~/reports/store';
import { issue } from '../mock_data/mock_data';
diff --git a/spec/frontend/reports/store/actions_spec.js b/spec/frontend/reports/store/actions_spec.js
index b7c4a31b1c8..25c3105466f 100644
--- a/spec/frontend/reports/store/actions_spec.js
+++ b/spec/frontend/reports/store/actions_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import {
setEndpoint,
@@ -13,8 +13,8 @@ import {
openModal,
closeModal,
} from '~/reports/store/actions';
-import state from '~/reports/store/state';
import * as types from '~/reports/store/mutation_types';
+import state from '~/reports/store/state';
describe('Reports Store Actions', () => {
let mockedState;
diff --git a/spec/frontend/reports/store/mutations_spec.js b/spec/frontend/reports/store/mutations_spec.js
index 59065d1151f..652b3b0ec45 100644
--- a/spec/frontend/reports/store/mutations_spec.js
+++ b/spec/frontend/reports/store/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/reports/store/state';
-import mutations from '~/reports/store/mutations';
import * as types from '~/reports/store/mutation_types';
+import mutations from '~/reports/store/mutations';
+import state from '~/reports/store/state';
import { issue } from '../mock_data/mock_data';
describe('Reports Store Mutations', () => {
diff --git a/spec/frontend/reports/store/utils_spec.js b/spec/frontend/reports/store/utils_spec.js
index 5249e9ffcce..cbc87bbb5ec 100644
--- a/spec/frontend/reports/store/utils_spec.js
+++ b/spec/frontend/reports/store/utils_spec.js
@@ -1,4 +1,3 @@
-import * as utils from '~/reports/store/utils';
import {
STATUS_FAILED,
STATUS_SUCCESS,
@@ -6,6 +5,7 @@ import {
ICON_SUCCESS,
ICON_NOTFOUND,
} from '~/reports/constants';
+import * as utils from '~/reports/store/utils';
describe('Reports store utils', () => {
describe('summaryTextbuilder', () => {
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index a0dc608ddc9..2ac2069a177 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { GlDropdown } from '@gitlab/ui';
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
let vm;
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index fe77057c3d4..ebea7dde34a 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import LastCommit from '~/repository/components/last_commit.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -58,77 +58,75 @@ describe('Repository last commit component', () => {
loading | label
${true} | ${'shows'}
${false} | ${'hides'}
- `('$label when loading icon $loading is true', ({ loading }) => {
+ `('$label when loading icon $loading is true', async ({ loading }) => {
factory(createCommitData(), loading);
- return vm.vm.$nextTick(() => {
- expect(vm.find(GlLoadingIcon).exists()).toBe(loading);
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find(GlLoadingIcon).exists()).toBe(loading);
});
- it('renders commit widget', () => {
+ it('renders commit widget', async () => {
factory();
- return vm.vm.$nextTick(() => {
- expect(vm.element).toMatchSnapshot();
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.element).toMatchSnapshot();
});
- it('renders short commit ID', () => {
+ it('renders short commit ID', async () => {
factory();
- return vm.vm.$nextTick(() => {
- expect(vm.find('[data-testid="last-commit-id-label"]').text()).toEqual('12345678');
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find('[data-testid="last-commit-id-label"]').text()).toEqual('12345678');
});
- it('hides pipeline components when pipeline does not exist', () => {
+ it('hides pipeline components when pipeline does not exist', async () => {
factory(createCommitData({ pipeline: null }));
- return vm.vm.$nextTick(() => {
- expect(vm.find('.js-commit-pipeline').exists()).toBe(false);
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find('.js-commit-pipeline').exists()).toBe(false);
});
- it('renders pipeline components', () => {
+ it('renders pipeline components', async () => {
factory();
- return vm.vm.$nextTick(() => {
- expect(vm.find('.js-commit-pipeline').exists()).toBe(true);
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find('.js-commit-pipeline').exists()).toBe(true);
});
- it('hides author component when author does not exist', () => {
+ it('hides author component when author does not exist', async () => {
factory(createCommitData({ author: null }));
- return vm.vm.$nextTick(() => {
- expect(vm.find('.js-user-link').exists()).toBe(false);
- expect(vm.find(UserAvatarLink).exists()).toBe(false);
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find('.js-user-link').exists()).toBe(false);
+ expect(vm.find(UserAvatarLink).exists()).toBe(false);
});
- it('does not render description expander when description is null', () => {
+ it('does not render description expander when description is null', async () => {
factory(createCommitData({ descriptionHtml: null }));
- return vm.vm.$nextTick(() => {
- expect(vm.find('.text-expander').exists()).toBe(false);
- expect(vm.find('.commit-row-description').exists()).toBe(false);
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find('.text-expander').exists()).toBe(false);
+ expect(vm.find('.commit-row-description').exists()).toBe(false);
});
- it('expands commit description when clicking expander', () => {
+ it('expands commit description when clicking expander', async () => {
factory(createCommitData({ descriptionHtml: 'Test description' }));
- return vm.vm
- .$nextTick()
- .then(() => {
- vm.find('.text-expander').vm.$emit('click');
- return vm.vm.$nextTick();
- })
- .then(() => {
- expect(vm.find('.commit-row-description').isVisible()).toBe(true);
- expect(vm.find('.text-expander').classes('open')).toBe(true);
- });
+ await vm.vm.$nextTick();
+
+ vm.find('.text-expander').vm.$emit('click');
+
+ await vm.vm.$nextTick();
+
+ expect(vm.find('.commit-row-description').isVisible()).toBe(true);
+ expect(vm.find('.text-expander').classes('open')).toBe(true);
});
it('strips the first newline of the description', async () => {
@@ -141,19 +139,19 @@ describe('Repository last commit component', () => {
);
});
- it('renders the signature HTML as returned by the backend', () => {
+ it('renders the signature HTML as returned by the backend', async () => {
factory(createCommitData({ signatureHtml: '<button>Verified</button>' }));
- return vm.vm.$nextTick().then(() => {
- expect(vm.element).toMatchSnapshot();
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.element).toMatchSnapshot();
});
- it('sets correct CSS class if the commit message is empty', () => {
+ it('sets correct CSS class if the commit message is empty', async () => {
factory(createCommitData({ message: '' }));
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('.item-title').classes()).toContain(emptyMessageClass);
- });
+ await vm.vm.$nextTick();
+
+ expect(vm.find('.item-title').classes()).toContain(emptyMessageClass);
});
});
diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js
index ebd985e640c..466eed52739 100644
--- a/spec/frontend/repository/components/preview/index_spec.js
+++ b/spec/frontend/repository/components/preview/index_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { handleLocationHash } from '~/lib/utils/common_utils';
import Preview from '~/repository/components/preview/index.vue';
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 1b8bbd5af6b..af263f43d7d 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Table from '~/repository/components/table/index.vue';
import TableRow from '~/repository/components/table/row.vue';
diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js
index b4800112fee..cf1ed272634 100644
--- a/spec/frontend/repository/components/table/parent_row_spec.js
+++ b/spec/frontend/repository/components/table/parent_row_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import ParentRow from '~/repository/components/table/parent_row.vue';
let vm;
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 767b117c798..69cb69de5df 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { GlBadge, GlLink, GlIcon } from '@gitlab/ui';
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index 70dbfaea551..2930e39df8a 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import TreeContent, { INITIAL_FETCH_COUNT } from '~/repository/components/tree_content.vue';
import FilePreview from '~/repository/components/preview/index.vue';
import FileTable from '~/repository/components/table/index.vue';
+import TreeContent, { INITIAL_FETCH_COUNT } from '~/repository/components/tree_content.vue';
let vm;
let $apollo;
diff --git a/spec/frontend/repository/utils/dom_spec.js b/spec/frontend/repository/utils/dom_spec.js
index f9e619a82d1..9839ddc6cdb 100644
--- a/spec/frontend/repository/utils/dom_spec.js
+++ b/spec/frontend/repository/utils/dom_spec.js
@@ -1,5 +1,5 @@
-import { TEST_HOST } from 'helpers/test_constants';
import { setHTMLFixture } from 'helpers/fixtures';
+import { TEST_HOST } from 'helpers/test_constants';
import { updateElementsVisibility, updateFormAction } from '~/repository/utils/dom';
describe('updateElementsVisibility', () => {
diff --git a/spec/frontend/right_sidebar_spec.js b/spec/frontend/right_sidebar_spec.js
index 1b31030cb92..f3719b28baa 100644
--- a/spec/frontend/right_sidebar_spec.js
+++ b/spec/frontend/right_sidebar_spec.js
@@ -1,5 +1,5 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import '~/commons/bootstrap';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar';
diff --git a/spec/frontend/search/highlight_blob_search_result_spec.js b/spec/frontend/search/highlight_blob_search_result_spec.js
index 112e6f5124f..c1b0c7d794b 100644
--- a/spec/frontend/search/highlight_blob_search_result_spec.js
+++ b/spec/frontend/search/highlight_blob_search_result_spec.js
@@ -1,6 +1,7 @@
import setHighlightClass from '~/search/highlight_blob_search_result';
const fixture = 'search/blob_search_result.html';
+const searchKeyword = 'Send'; // spec/frontend/fixtures/search.rb#79
describe('search/highlight_blob_search_result', () => {
preloadFixtures(fixture);
@@ -8,7 +9,7 @@ describe('search/highlight_blob_search_result', () => {
beforeEach(() => loadFixtures(fixture));
it('highlights lines with search term occurrence', () => {
- setHighlightClass();
+ setHighlightClass(searchKeyword);
expect(document.querySelectorAll('.blob-result .hll').length).toBe(4);
});
diff --git a/spec/frontend/search/index_spec.js b/spec/frontend/search/index_spec.js
index 023cd341345..1992a7f4437 100644
--- a/spec/frontend/search/index_spec.js
+++ b/spec/frontend/search/index_spec.js
@@ -1,9 +1,11 @@
+import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
import { initSearchApp } from '~/search';
import createStore from '~/search/store';
jest.mock('~/search/store');
jest.mock('~/search/topbar');
jest.mock('~/search/sidebar');
+jest.mock('ee_else_ce/search/highlight_blob_search_result');
describe('initSearchApp', () => {
let defaultLocation;
@@ -42,6 +44,7 @@ describe('initSearchApp', () => {
it(`decodes ${search} to ${decodedSearch}`, () => {
expect(createStore).toHaveBeenCalledWith({ query: { search: decodedSearch } });
+ expect(setHighlightClass).toHaveBeenCalledWith(decodedSearch);
});
});
});
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index ee509eaad8d..d076997b04a 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -26,7 +26,7 @@ export const MOCK_GROUPS = [
export const MOCK_PROJECT = {
name: 'test project',
- namespace_id: MOCK_GROUP.id,
+ namespace: MOCK_GROUP,
nameWithNamespace: 'test group test project',
id: 'test_1',
};
@@ -34,14 +34,30 @@ export const MOCK_PROJECT = {
export const MOCK_PROJECTS = [
{
name: 'test project',
- namespace_id: MOCK_GROUP.id,
+ namespace: MOCK_GROUP,
name_with_namespace: 'test group test project',
id: 'test_1',
},
{
name: 'test project 2',
- namespace_id: MOCK_GROUP.id,
+ namespace: MOCK_GROUP,
name_with_namespace: 'test group test project 2',
id: 'test_2',
},
];
+
+export const MOCK_SORT_OPTIONS = [
+ {
+ title: 'Most relevant',
+ sortable: false,
+ sortParam: 'relevant',
+ },
+ {
+ title: 'Created date',
+ sortable: true,
+ sortParam: {
+ asc: 'created_asc',
+ desc: 'created_desc',
+ },
+ },
+];
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index 94a39b90d02..b93527c1fe9 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlButton, GlLink } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import GlobalSearchSidebar from '~/search/sidebar/components/app.vue';
import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
diff --git a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
index 42fcc859308..3713e1d414f 100644
--- a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
import RadioFilter from '~/search/sidebar/components/radio_filter.vue';
diff --git a/spec/frontend/search/sidebar/components/radio_filter_spec.js b/spec/frontend/search/sidebar/components/radio_filter_spec.js
index 9918af54cfe..4c81312e479 100644
--- a/spec/frontend/search/sidebar/components/radio_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/radio_filter_spec.js
@@ -1,10 +1,10 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import RadioFilter from '~/search/sidebar/components/radio_filter.vue';
-import { stateFilterData } from '~/search/sidebar/constants/state_filter_data';
import { confidentialFilterData } from '~/search/sidebar/constants/confidential_filter_data';
+import { stateFilterData } from '~/search/sidebar/constants/state_filter_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/search/sidebar/components/status_filter_spec.js b/spec/frontend/search/sidebar/components/status_filter_spec.js
index 21fc663397e..08ce57b206b 100644
--- a/spec/frontend/search/sidebar/components/status_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/status_filter_spec.js
@@ -1,8 +1,8 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
-import StatusFilter from '~/search/sidebar/components/status_filter.vue';
import RadioFilter from '~/search/sidebar/components/radio_filter.vue';
+import StatusFilter from '~/search/sidebar/components/status_filter.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/search/sort/components/app_spec.js b/spec/frontend/search/sort/components/app_spec.js
new file mode 100644
index 00000000000..5806d6b51d2
--- /dev/null
+++ b/spec/frontend/search/sort/components/app_spec.js
@@ -0,0 +1,168 @@
+import { GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { MOCK_QUERY, MOCK_SORT_OPTIONS } from 'jest/search/mock_data';
+import GlobalSearchSort from '~/search/sort/components/app.vue';
+import { SORT_DIRECTION_UI } from '~/search/sort/constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('GlobalSearchSort', () => {
+ let wrapper;
+
+ const actionSpies = {
+ setQuery: jest.fn(),
+ applyQuery: jest.fn(),
+ };
+
+ const defaultProps = {
+ searchSortOptions: MOCK_SORT_OPTIONS,
+ };
+
+ const createComponent = (initialState, props) => {
+ const store = new Vuex.Store({
+ state: {
+ query: MOCK_QUERY,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = shallowMount(GlobalSearchSort, {
+ localVue,
+ store,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findSortButtonGroup = () => wrapper.find(GlButtonGroup);
+ const findSortDropdown = () => wrapper.find(GlDropdown);
+ const findSortDirectionButton = () => wrapper.find(GlButton);
+ const findDropdownItems = () => findSortDropdown().findAll(GlDropdownItem);
+ const findDropdownItemsText = () => findDropdownItems().wrappers.map((w) => w.text());
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders Sort Button Group', () => {
+ expect(findSortButtonGroup().exists()).toBe(true);
+ });
+
+ it('renders Sort Dropdown', () => {
+ expect(findSortDropdown().exists()).toBe(true);
+ });
+
+ it('renders Sort Direction Button', () => {
+ expect(findSortDirectionButton().exists()).toBe(true);
+ });
+ });
+
+ describe('Sort Dropdown Items', () => {
+ describe('renders', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('an instance for each namespace', () => {
+ expect(findDropdownItemsText()).toStrictEqual(
+ MOCK_SORT_OPTIONS.map((option) => option.title),
+ );
+ });
+ });
+
+ describe.each`
+ sortQuery | value
+ ${null} | ${MOCK_SORT_OPTIONS[0].title}
+ ${'asdf'} | ${MOCK_SORT_OPTIONS[0].title}
+ ${MOCK_SORT_OPTIONS[0].sortParam} | ${MOCK_SORT_OPTIONS[0].title}
+ ${MOCK_SORT_OPTIONS[1].sortParam.desc} | ${MOCK_SORT_OPTIONS[1].title}
+ ${MOCK_SORT_OPTIONS[1].sortParam.asc} | ${MOCK_SORT_OPTIONS[1].title}
+ `('selected', ({ sortQuery, value }) => {
+ describe(`when sort option is ${sortQuery}`, () => {
+ beforeEach(() => {
+ createComponent({ query: { sort: sortQuery } });
+ });
+
+ it('is set correctly', () => {
+ expect(findSortDropdown().attributes('text')).toBe(value);
+ });
+ });
+ });
+ });
+
+ describe.each`
+ description | sortQuery | sortUi | disabled
+ ${'non-sortable'} | ${MOCK_SORT_OPTIONS[0].sortParam} | ${SORT_DIRECTION_UI.disabled} | ${'true'}
+ ${'descending sortable'} | ${MOCK_SORT_OPTIONS[1].sortParam.desc} | ${SORT_DIRECTION_UI.desc} | ${undefined}
+ ${'ascending sortable'} | ${MOCK_SORT_OPTIONS[1].sortParam.asc} | ${SORT_DIRECTION_UI.asc} | ${undefined}
+ `('Sort Direction Button', ({ description, sortQuery, sortUi, disabled }) => {
+ describe(`when sort option is ${description}`, () => {
+ beforeEach(() => {
+ createComponent({ query: { sort: sortQuery } });
+ });
+
+ it('sets the UI correctly', () => {
+ expect(findSortDirectionButton().attributes('disabled')).toBe(disabled);
+ expect(findSortDirectionButton().attributes('title')).toBe(sortUi.tooltip);
+ expect(findSortDirectionButton().attributes('icon')).toBe(sortUi.icon);
+ });
+ });
+ });
+
+ describe('actions', () => {
+ describe.each`
+ description | index | value
+ ${'non-sortable'} | ${0} | ${MOCK_SORT_OPTIONS[0].sortParam}
+ ${'sortable'} | ${1} | ${MOCK_SORT_OPTIONS[1].sortParam.desc}
+ `('handleSortChange', ({ description, index, value }) => {
+ describe(`when clicking a ${description} option`, () => {
+ beforeEach(() => {
+ createComponent();
+ findDropdownItems().at(index).vm.$emit('click');
+ });
+
+ it('calls setQuery and applyQuery correctly', () => {
+ expect(actionSpies.setQuery).toHaveBeenCalledTimes(1);
+ expect(actionSpies.applyQuery).toHaveBeenCalledTimes(1);
+ expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
+ key: 'sort',
+ value,
+ });
+ });
+ });
+ });
+
+ describe.each`
+ description | sortQuery | value
+ ${'descending'} | ${MOCK_SORT_OPTIONS[1].sortParam.desc} | ${MOCK_SORT_OPTIONS[1].sortParam.asc}
+ ${'ascending'} | ${MOCK_SORT_OPTIONS[1].sortParam.asc} | ${MOCK_SORT_OPTIONS[1].sortParam.desc}
+ `('handleSortDirectionChange', ({ description, sortQuery, value }) => {
+ describe(`when toggling a ${description} option`, () => {
+ beforeEach(() => {
+ createComponent({ query: { sort: sortQuery } });
+ findSortDirectionButton().vm.$emit('click');
+ });
+
+ it('calls setQuery and applyQuery correctly', () => {
+ expect(actionSpies.setQuery).toHaveBeenCalledTimes(1);
+ expect(actionSpies.applyQuery).toHaveBeenCalledTimes(1);
+ expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
+ key: 'sort',
+ value,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index e4536a3e136..ab622c53387 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -1,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import * as urlUtils from '~/lib/utils/url_utility';
import * as actions from '~/search/store/actions';
import * as types from '~/search/store/mutation_types';
-import * as urlUtils from '~/lib/utils/url_utility';
import createState from '~/search/store/state';
-import axios from '~/lib/utils/axios_utils';
-import createFlash from '~/flash';
import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECT, MOCK_PROJECTS } from '../mock_data';
jest.mock('~/flash');
diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js
index 560ed66263b..df94ba40ff2 100644
--- a/spec/frontend/search/store/mutations_spec.js
+++ b/spec/frontend/search/store/mutations_spec.js
@@ -1,6 +1,6 @@
+import * as types from '~/search/store/mutation_types';
import mutations from '~/search/store/mutations';
import createState from '~/search/store/state';
-import * as types from '~/search/store/mutation_types';
import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data';
describe('Global Search Store Mutations', () => {
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
new file mode 100644
index 00000000000..fb953f2ed1b
--- /dev/null
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -0,0 +1,113 @@
+import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { MOCK_QUERY } from 'jest/search/mock_data';
+import GlobalSearchTopbar from '~/search/topbar/components/app.vue';
+import GroupFilter from '~/search/topbar/components/group_filter.vue';
+import ProjectFilter from '~/search/topbar/components/project_filter.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('GlobalSearchTopbar', () => {
+ let wrapper;
+
+ const actionSpies = {
+ applyQuery: jest.fn(),
+ setQuery: jest.fn(),
+ };
+
+ const createComponent = (initialState) => {
+ const store = new Vuex.Store({
+ state: {
+ query: MOCK_QUERY,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = shallowMount(GlobalSearchTopbar, {
+ localVue,
+ store,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findTopbarForm = () => wrapper.find(GlForm);
+ const findGlSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findGroupFilter = () => wrapper.find(GroupFilter);
+ const findProjectFilter = () => wrapper.find(ProjectFilter);
+ const findSearchButton = () => wrapper.find(GlButton);
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders Topbar Form always', () => {
+ expect(findTopbarForm().exists()).toBe(true);
+ });
+
+ describe('Search box', () => {
+ it('renders always', () => {
+ expect(findGlSearchBox().exists()).toBe(true);
+ });
+
+ describe('onSearch', () => {
+ const testSearch = 'test search';
+
+ beforeEach(() => {
+ findGlSearchBox().vm.$emit('input', testSearch);
+ });
+
+ it('calls setQuery when input event is fired from GlSearchBoxByType', () => {
+ expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
+ key: 'search',
+ value: testSearch,
+ });
+ });
+ });
+ });
+
+ describe.each`
+ snippets | showFilters
+ ${null} | ${true}
+ ${{ query: { snippets: '' } }} | ${true}
+ ${{ query: { snippets: false } }} | ${true}
+ ${{ query: { snippets: true } }} | ${false}
+ ${{ query: { snippets: 'false' } }} | ${true}
+ ${{ query: { snippets: 'true' } }} | ${false}
+ `('topbar filters', ({ snippets, showFilters }) => {
+ beforeEach(() => {
+ createComponent(snippets);
+ });
+
+ it(`does${showFilters ? '' : ' not'} render when snippets is ${JSON.stringify(
+ snippets,
+ )}`, () => {
+ expect(findGroupFilter().exists()).toBe(showFilters);
+ expect(findProjectFilter().exists()).toBe(showFilters);
+ });
+ });
+
+ it('renders SearchButton always', () => {
+ expect(findSearchButton().exists()).toBe(true);
+ });
+ });
+
+ describe('actions', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('clicking SearchButton calls applyQuery', () => {
+ findTopbarForm().vm.$emit('submit', { preventDefault: () => {} });
+
+ expect(actionSpies.applyQuery).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/topbar/components/group_filter_spec.js
index 017808d576e..15b46f9c058 100644
--- a/spec/frontend/search/topbar/components/group_filter_spec.js
+++ b/spec/frontend/search/topbar/components/group_filter_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import GroupFilter from '~/search/topbar/components/group_filter.vue';
diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/topbar/components/project_filter_spec.js
index c1fc61d7e89..3bd0769b34a 100644
--- a/spec/frontend/search/topbar/components/project_filter_spec.js
+++ b/spec/frontend/search/topbar/components/project_filter_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_PROJECT, MOCK_QUERY } from 'jest/search/mock_data';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import ProjectFilter from '~/search/topbar/components/project_filter.vue';
@@ -99,7 +99,7 @@ describe('ProjectFilter', () => {
it('calls setUrlParams with project id, group id, then calls visitUrl', () => {
expect(setUrlParams).toHaveBeenCalledWith({
- [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace_id,
+ [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace.id,
[PROJECT_DATA.queryParam]: MOCK_PROJECT.id,
});
expect(visitUrl).toHaveBeenCalled();
diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
index 86e29571d0f..5de948592d4 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 Vuex from 'vuex';
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
+import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import Vuex from 'vuex';
import { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data';
import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
import { ANY_OPTION, GROUP_DATA } from '~/search/topbar/constants';
diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js
index e844bc96e05..a9fbe0fe552 100644
--- a/spec/frontend/search_autocomplete_spec.js
+++ b/spec/frontend/search_autocomplete_spec.js
@@ -1,11 +1,10 @@
/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */
-
-import $ from 'jquery';
import AxiosMockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import axios from '~/lib/utils/axios_utils';
import initSearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
-import axios from '~/lib/utils/axios_utils';
describe('Search autocomplete dropdown', () => {
let widget = null;
diff --git a/spec/frontend/search_settings/components/search_settings_spec.js b/spec/frontend/search_settings/components/search_settings_spec.js
index b80f9b15abf..173936e1ce3 100644
--- a/spec/frontend/search_settings/components/search_settings_spec.js
+++ b/spec/frontend/search_settings/components/search_settings_spec.js
@@ -2,6 +2,7 @@ import { GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SearchSettings from '~/search_settings/components/search_settings.vue';
import { HIGHLIGHT_CLASS, HIDE_CLASS } from '~/search_settings/constants';
+import { isExpanded, expandSection, closeSection } from '~/settings_panels';
describe('search_settings/components/search_settings.vue', () => {
const ROOT_ID = 'content-body';
@@ -9,6 +10,8 @@ describe('search_settings/components/search_settings.vue', () => {
const SEARCH_TERM = 'Delete project';
const GENERAL_SETTINGS_ID = 'js-general-settings';
const ADVANCED_SETTINGS_ID = 'js-advanced-settings';
+ const EXTRA_SETTINGS_ID = 'js-extra-settings';
+
let wrapper;
const buildWrapper = () => {
@@ -16,10 +19,15 @@ describe('search_settings/components/search_settings.vue', () => {
propsData: {
searchRoot: document.querySelector(`#${ROOT_ID}`),
sectionSelector: SECTION_SELECTOR,
+ isExpandedFn: isExpanded,
+ },
+ // Add real listeners so we can simplify and strengthen some tests.
+ listeners: {
+ expand: expandSection,
+ collapse: closeSection,
},
});
};
-
const sections = () => Array.from(document.querySelectorAll(SECTION_SELECTOR));
const sectionsCount = () => sections().length;
const visibleSectionsCount = () =>
@@ -39,7 +47,10 @@ describe('search_settings/components/search_settings.vue', () => {
<section id="${GENERAL_SETTINGS_ID}" class="settings">
<span>General</span>
</section>
- <section id="${ADVANCED_SETTINGS_ID}" class="settings">
+ <section id="${ADVANCED_SETTINGS_ID}" class="settings expanded">
+ <span>Advanced</span>
+ </section>
+ <section id="${EXTRA_SETTINGS_ID}" class="settings">
<span>${SEARCH_TERM}</span>
</section>
</div>
@@ -52,17 +63,6 @@ describe('search_settings/components/search_settings.vue', () => {
wrapper.destroy();
});
- it('expands first section and collapses the rest', () => {
- clearSearch();
-
- const [firstSection, ...otherSections] = sections();
-
- expect(wrapper.emitted()).toEqual({
- expand: [[firstSection]],
- collapse: otherSections.map((x) => [x]),
- });
- });
-
it('hides sections that do not match the search term', () => {
const hiddenSection = document.querySelector(`#${GENERAL_SETTINGS_ID}`);
search(SEARCH_TERM);
@@ -72,12 +72,11 @@ describe('search_settings/components/search_settings.vue', () => {
});
it('expands section that matches the search term', () => {
- const section = document.querySelector(`#${ADVANCED_SETTINGS_ID}`);
+ const section = document.querySelector(`#${EXTRA_SETTINGS_ID}`);
search(SEARCH_TERM);
- // Last called because expand is always called once to reset the page state
- expect(wrapper.emitted().expand[1][0]).toBe(section);
+ expect(wrapper.emitted('expand')).toEqual([[section]]);
});
it('highlight elements that match the search term', () => {
@@ -86,21 +85,64 @@ describe('search_settings/components/search_settings.vue', () => {
expect(highlightedElementsCount()).toBe(1);
});
- describe('when search term is cleared', () => {
- beforeEach(() => {
- search(SEARCH_TERM);
- });
-
- it('displays all sections', () => {
- expect(visibleSectionsCount()).toBe(1);
- clearSearch();
- expect(visibleSectionsCount()).toBe(sectionsCount());
+ describe('default', () => {
+ it('test setup starts with expansion state', () => {
+ expect(sections().map(isExpanded)).toEqual([false, true, false]);
});
- it('removes the highlight from all elements', () => {
- expect(highlightedElementsCount()).toBe(1);
- clearSearch();
- expect(highlightedElementsCount()).toBe(0);
+ describe('when searched and cleared', () => {
+ beforeEach(() => {
+ search('Test');
+ clearSearch();
+ });
+
+ it('displays all sections', () => {
+ expect(visibleSectionsCount()).toBe(sectionsCount());
+ });
+
+ it('removes the highlight from all elements', () => {
+ expect(highlightedElementsCount()).toBe(0);
+ });
+
+ it('should preserve original expansion state', () => {
+ expect(sections().map(isExpanded)).toEqual([false, true, false]);
+ });
+
+ it('should preserve state by emitting events', () => {
+ const [first, mid, last] = sections();
+
+ expect(wrapper.emitted()).toEqual({
+ expand: [[mid]],
+ collapse: [[first], [last]],
+ });
+ });
+
+ describe('after multiple searches and clear', () => {
+ beforeEach(() => {
+ search('Test');
+ search(SEARCH_TERM);
+ clearSearch();
+ });
+
+ it('should preserve last expansion state', () => {
+ expect(sections().map(isExpanded)).toEqual([false, true, false]);
+ });
+ });
+
+ describe('after user expands and collapses, search, and clear', () => {
+ beforeEach(() => {
+ const [first, mid] = sections();
+ closeSection(mid);
+ expandSection(first);
+
+ search(SEARCH_TERM);
+ clearSearch();
+ });
+
+ it('should preserve last expansion state', () => {
+ expect(sections().map(isExpanded)).toEqual([true, false, false]);
+ });
+ });
});
});
});
diff --git a/spec/frontend/search_settings/index_spec.js b/spec/frontend/search_settings/index_spec.js
index 122ee1251bb..1d56d054eea 100644
--- a/spec/frontend/search_settings/index_spec.js
+++ b/spec/frontend/search_settings/index_spec.js
@@ -1,36 +1,25 @@
-import $ from 'jquery';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initSearch from '~/search_settings';
-import { expandSection, closeSection } from '~/settings_panels';
+import mount from '~/search_settings/mount';
-jest.mock('~/settings_panels');
-
-describe('search_settings/index', () => {
- let app;
-
- beforeEach(() => {
- const el = document.createElement('div');
-
- setHTMLFixture('<div id="content-body"></div>');
-
- app = initSearch({ el });
- });
+jest.mock('~/search_settings/mount');
+describe('~/search_settings', () => {
afterEach(() => {
- app.$destroy();
+ resetHTMLFixture();
});
- it('calls settings_panel.onExpand when expand event is emitted', () => {
- const section = { name: 'section' };
- app.$refs.searchSettings.$emit('expand', section);
+ it('initializes search settings when js-search-settings-app is available', async () => {
+ setHTMLFixture('<div class="js-search-settings-app"></div>');
+
+ await initSearch();
- expect(expandSection).toHaveBeenCalledWith($(section));
+ expect(mount).toHaveBeenCalled();
});
- it('calls settings_panel.closeSection when collapse event is emitted', () => {
- const section = { name: 'section' };
- app.$refs.searchSettings.$emit('collapse', section);
+ it('does not initialize search settings when js-search-settings-app is unavailable', async () => {
+ await initSearch();
- expect(closeSection).toHaveBeenCalledWith($(section));
+ expect(mount).not.toHaveBeenCalled();
});
});
diff --git a/spec/frontend/search_settings/mount_spec.js b/spec/frontend/search_settings/mount_spec.js
new file mode 100644
index 00000000000..8c141c4704e
--- /dev/null
+++ b/spec/frontend/search_settings/mount_spec.js
@@ -0,0 +1,35 @@
+import { setHTMLFixture } from 'helpers/fixtures';
+import mount from '~/search_settings/mount';
+import { expandSection, closeSection } from '~/settings_panels';
+
+jest.mock('~/settings_panels');
+
+describe('search_settings/mount', () => {
+ let app;
+
+ beforeEach(() => {
+ const el = document.createElement('div');
+
+ setHTMLFixture('<div id="content-body"></div>');
+
+ app = mount({ el });
+ });
+
+ afterEach(() => {
+ app.$destroy();
+ });
+
+ it('calls settings_panel.onExpand when expand event is emitted', () => {
+ const section = { name: 'section' };
+ app.$refs.searchSettings.$emit('expand', section);
+
+ expect(expandSection).toHaveBeenCalledWith(section);
+ });
+
+ it('calls settings_panel.closeSection when collapse event is emitted', () => {
+ const section = { name: 'section' };
+ app.$refs.searchSettings.$emit('collapse', section);
+
+ expect(closeSection).toHaveBeenCalledWith(section);
+ });
+});
diff --git a/spec/frontend/search_spec.js b/spec/frontend/search_spec.js
deleted file mode 100644
index d234a7fccb9..00000000000
--- a/spec/frontend/search_spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
-import Search from '~/pages/search/show/search';
-
-jest.mock('~/api');
-jest.mock('ee_else_ce/search/highlight_blob_search_result');
-
-describe('Search', () => {
- const fixturePath = 'search/show.html';
-
- preloadFixtures(fixturePath);
-
- describe('constructor side effects', () => {
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('highlights lines with search terms in blob search results', () => {
- new Search(); // eslint-disable-line no-new
-
- expect(setHighlightClass).toHaveBeenCalled();
- });
- });
-});
diff --git a/spec/frontend/security_configuration/app_spec.js b/spec/frontend/security_configuration/app_spec.js
new file mode 100644
index 00000000000..11d481fb210
--- /dev/null
+++ b/spec/frontend/security_configuration/app_spec.js
@@ -0,0 +1,27 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/security_configuration/components/app.vue';
+import ConfigurationTable from '~/security_configuration/components/configuration_table.vue';
+
+describe('App Component', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(App, {});
+ };
+ const findConfigurationTable = () => wrapper.findComponent(ConfigurationTable);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders correct primary & Secondary Heading', () => {
+ createComponent();
+ expect(wrapper.text()).toContain('Security Configuration');
+ expect(wrapper.text()).toContain('Testing & Compliance');
+ });
+
+ it('renders ConfigurationTable Component', () => {
+ createComponent();
+ expect(findConfigurationTable().exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/security_configuration/configuration_table_spec.js b/spec/frontend/security_configuration/configuration_table_spec.js
new file mode 100644
index 00000000000..49f9a7a3ea8
--- /dev/null
+++ b/spec/frontend/security_configuration/configuration_table_spec.js
@@ -0,0 +1,48 @@
+import { mount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import ConfigurationTable from '~/security_configuration/components/configuration_table.vue';
+import { features, UPGRADE_CTA } from '~/security_configuration/components/features_constants';
+
+import {
+ REPORT_TYPE_SAST,
+ REPORT_TYPE_DAST,
+ REPORT_TYPE_DEPENDENCY_SCANNING,
+ REPORT_TYPE_CONTAINER_SCANNING,
+ REPORT_TYPE_COVERAGE_FUZZING,
+ REPORT_TYPE_LICENSE_COMPLIANCE,
+} from '~/vue_shared/security_reports/constants';
+
+describe('Configuration Table Component', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = extendedWrapper(mount(ConfigurationTable, {}));
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each(features)('should match strings', (feature) => {
+ expect(wrapper.text()).toContain(feature.name);
+ expect(wrapper.text()).toContain(feature.description);
+
+ if (feature.type === REPORT_TYPE_SAST) {
+ expect(wrapper.findByTestId(feature.type).text()).toBe('Configure via Merge Request');
+ } else if (
+ [
+ REPORT_TYPE_DAST,
+ REPORT_TYPE_DEPENDENCY_SCANNING,
+ REPORT_TYPE_CONTAINER_SCANNING,
+ REPORT_TYPE_COVERAGE_FUZZING,
+ REPORT_TYPE_LICENSE_COMPLIANCE,
+ ].includes(feature.type)
+ ) {
+ expect(wrapper.findByTestId(feature.type).text()).toMatchInterpolatedText(UPGRADE_CTA);
+ }
+ });
+});
diff --git a/spec/frontend/security_configuration/manage_sast_spec.js b/spec/frontend/security_configuration/manage_sast_spec.js
new file mode 100644
index 00000000000..7c76f19ddb4
--- /dev/null
+++ b/spec/frontend/security_configuration/manage_sast_spec.js
@@ -0,0 +1,136 @@
+import { GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { redirectTo } from '~/lib/utils/url_utility';
+import ManageSast from '~/security_configuration/components/manage_sast.vue';
+import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+}));
+
+Vue.use(VueApollo);
+
+describe('Manage Sast Component', () => {
+ let wrapper;
+
+ const findButton = () => wrapper.findComponent(GlButton);
+ const successHandler = async () => {
+ return {
+ data: {
+ configureSast: {
+ successPath: 'testSuccessPath',
+ errors: [],
+ __typename: 'ConfigureSastPayload',
+ },
+ },
+ };
+ };
+
+ const noSuccessPathHandler = async () => {
+ return {
+ data: {
+ configureSast: {
+ successPath: '',
+ errors: [],
+ __typename: 'ConfigureSastPayload',
+ },
+ },
+ };
+ };
+
+ const errorHandler = async () => {
+ return {
+ data: {
+ configureSast: {
+ successPath: 'testSuccessPath',
+ errors: ['foo'],
+ __typename: 'ConfigureSastPayload',
+ },
+ },
+ };
+ };
+
+ const pendingHandler = () => new Promise(() => {});
+
+ function createMockApolloProvider(handler) {
+ const requestHandlers = [[configureSastMutation, handler]];
+
+ return createMockApollo(requestHandlers);
+ }
+
+ function createComponent(options = {}) {
+ const { mockApollo } = options;
+ wrapper = extendedWrapper(
+ mount(ManageSast, {
+ apolloProvider: mockApollo,
+ }),
+ );
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render Button with correct text', () => {
+ createComponent();
+ expect(findButton().text()).toContain('Configure via Merge Request');
+ });
+
+ describe('given a successful response', () => {
+ beforeEach(() => {
+ const mockApollo = createMockApolloProvider(successHandler);
+ createComponent({ mockApollo });
+ });
+
+ it('should call redirect helper with correct value', async () => {
+ await wrapper.trigger('click');
+ await waitForPromises();
+ expect(redirectTo).toHaveBeenCalledTimes(1);
+ expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
+ // This is done for UX reasons. If the loading prop is set to false
+ // on success, then there's a period where the button is clickable
+ // again. Instead, we want the button to display a loading indicator
+ // for the remainder of the lifetime of the page (i.e., until the
+ // browser can start painting the new page it's been redirected to).
+ expect(findButton().props().loading).toBe(true);
+ });
+ });
+
+ describe('given a pending response', () => {
+ beforeEach(() => {
+ const mockApollo = createMockApolloProvider(pendingHandler);
+ createComponent({ mockApollo });
+ });
+
+ it('renders spinner correctly', async () => {
+ expect(findButton().props('loading')).toBe(false);
+ await wrapper.trigger('click');
+ await waitForPromises();
+ expect(findButton().props('loading')).toBe(true);
+ });
+ });
+
+ describe.each`
+ handler | message
+ ${noSuccessPathHandler} | ${'SAST merge request creation mutation failed'}
+ ${errorHandler} | ${'foo'}
+ `('given an error response', ({ handler, message }) => {
+ beforeEach(() => {
+ const mockApollo = createMockApolloProvider(handler);
+ createComponent({ mockApollo });
+ });
+
+ it('should catch and emit error', async () => {
+ await wrapper.trigger('click');
+ await waitForPromises();
+ expect(wrapper.emitted('error')).toEqual([[message]]);
+ expect(findButton().props('loading')).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/security_configuration/upgrade_spec.js b/spec/frontend/security_configuration/upgrade_spec.js
new file mode 100644
index 00000000000..0ab1108b265
--- /dev/null
+++ b/spec/frontend/security_configuration/upgrade_spec.js
@@ -0,0 +1,29 @@
+import { mount } from '@vue/test-utils';
+import { UPGRADE_CTA } from '~/security_configuration/components/features_constants';
+import Upgrade from '~/security_configuration/components/upgrade.vue';
+
+let wrapper;
+const createComponent = () => {
+ wrapper = mount(Upgrade, {});
+};
+
+beforeEach(() => {
+ createComponent();
+});
+
+afterEach(() => {
+ wrapper.destroy();
+});
+
+describe('Upgrade component', () => {
+ it('renders correct text in link', () => {
+ expect(wrapper.text()).toMatchInterpolatedText(UPGRADE_CTA);
+ });
+
+ it('renders link with correct attributes', () => {
+ expect(wrapper.find('a').attributes()).toMatchObject({
+ href: 'https://about.gitlab.com/pricing/',
+ target: '_blank',
+ });
+ });
+});
diff --git a/spec/frontend/self_monitor/components/self_monitor_form_spec.js b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
index dfa961c5115..5f5934305c6 100644
--- a/spec/frontend/self_monitor/components/self_monitor_form_spec.js
+++ b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import SelfMonitor from '~/self_monitor/components/self_monitor_form.vue';
import { createStore } from '~/self_monitor/store';
diff --git a/spec/frontend/sentry/index_spec.js b/spec/frontend/sentry/index_spec.js
index 82b6c445d96..13b9b9e909c 100644
--- a/spec/frontend/sentry/index_spec.js
+++ b/spec/frontend/sentry/index_spec.js
@@ -1,5 +1,5 @@
-import SentryConfig from '~/sentry/sentry_config';
import index from '~/sentry/index';
+import SentryConfig from '~/sentry/sentry_config';
describe('SentryConfig options', () => {
const dsn = 'https://123@sentry.gitlab.test/123';
diff --git a/spec/frontend/sentry/sentry_config_spec.js b/spec/frontend/sentry/sentry_config_spec.js
index 5ee261f480a..f7102f9b2f9 100644
--- a/spec/frontend/sentry/sentry_config_spec.js
+++ b/spec/frontend/sentry/sentry_config_spec.js
@@ -1,5 +1,5 @@
-import * as Sentry from '~/sentry/wrapper';
import SentryConfig from '~/sentry/sentry_config';
+import * as Sentry from '~/sentry/wrapper';
describe('SentryConfig', () => {
describe('IGNORE_ERRORS', () => {
diff --git a/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js b/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js
index 135a3844e78..772d6903052 100644
--- a/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js
+++ b/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js
@@ -1,6 +1,6 @@
+import { GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
import SentryErrorStackTrace from '~/sentry_error_stack_trace/components/sentry_error_stack_trace.vue';
diff --git a/spec/frontend/serverless/components/empty_state_spec.js b/spec/frontend/serverless/components/empty_state_spec.js
index daa1576a4ec..d63882c2a6d 100644
--- a/spec/frontend/serverless/components/empty_state_spec.js
+++ b/spec/frontend/serverless/components/empty_state_spec.js
@@ -1,7 +1,7 @@
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { createStore } from '~/serverless/store';
import EmptyStateComponent from '~/serverless/components/empty_state.vue';
+import { createStore } from '~/serverless/store';
describe('EmptyStateComponent', () => {
let wrapper;
diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js
index a59b4fdbb7b..944283136d0 100644
--- a/spec/frontend/serverless/components/environment_row_spec.js
+++ b/spec/frontend/serverless/components/environment_row_spec.js
@@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import environmentRowComponent from '~/serverless/components/environment_row.vue';
-import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
import { translate } from '~/serverless/utils';
+import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
const createComponent = (env, envName) =>
shallowMount(environmentRowComponent, {
diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js
index 248dd418941..d2b8de71e01 100644
--- a/spec/frontend/serverless/components/function_details_spec.js
+++ b/spec/frontend/serverless/components/function_details_spec.js
@@ -1,6 +1,6 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import functionDetailsComponent from '~/serverless/components/function_details.vue';
import { createStore } from '~/serverless/store';
diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js
index 0fca027fe56..01dd512c5d3 100644
--- a/spec/frontend/serverless/components/functions_spec.js
+++ b/spec/frontend/serverless/components/functions_spec.js
@@ -1,13 +1,13 @@
-import Vuex from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
-import AxiosMockAdapter from 'axios-mock-adapter';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import functionsComponent from '~/serverless/components/functions.vue';
-import { createStore } from '~/serverless/store';
import EmptyState from '~/serverless/components/empty_state.vue';
import EnvironmentRow from '~/serverless/components/environment_row.vue';
+import functionsComponent from '~/serverless/components/functions.vue';
+import { createStore } from '~/serverless/store';
import { mockServerlessFunctions } from '../mock_data';
describe('functionsComponent', () => {
diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js
index ffdb1f13111..d5b187452c6 100644
--- a/spec/frontend/serverless/components/missing_prometheus_spec.js
+++ b/spec/frontend/serverless/components/missing_prometheus_spec.js
@@ -1,7 +1,7 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { createStore } from '~/serverless/store';
import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue';
+import { createStore } from '~/serverless/store';
describe('missingPrometheusComponent', () => {
let wrapper;
diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js
index c43933e5b94..8c839577aa0 100644
--- a/spec/frontend/serverless/components/url_spec.js
+++ b/spec/frontend/serverless/components/url_spec.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import urlComponent from '~/serverless/components/url.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js
index 32e30a57d4b..61b9bd121af 100644
--- a/spec/frontend/serverless/store/actions_spec.js
+++ b/spec/frontend/serverless/store/actions_spec.js
@@ -1,9 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions';
import { mockServerlessFunctions, mockMetrics } from '../mock_data';
-import axios from '~/lib/utils/axios_utils';
import { adjustMetricQuery } from '../utils';
describe('ServerlessActions', () => {
diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js
index 92853fda37c..e1942bd2759 100644
--- a/spec/frontend/serverless/store/getters_spec.js
+++ b/spec/frontend/serverless/store/getters_spec.js
@@ -1,5 +1,5 @@
-import serverlessState from '~/serverless/store/state';
import * as getters from '~/serverless/store/getters';
+import serverlessState from '~/serverless/store/state';
import { mockServerlessFunctions } from '../mock_data';
describe('Serverless Store Getters', () => {
diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js
index e2771c7e5fd..a1a8f9a2ca7 100644
--- a/spec/frontend/serverless/store/mutations_spec.js
+++ b/spec/frontend/serverless/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/serverless/store/mutations';
import * as types from '~/serverless/store/mutation_types';
+import mutations from '~/serverless/store/mutations';
import { mockServerlessFunctions, mockMetrics } from '../mock_data';
describe('ServerlessMutations', () => {
diff --git a/spec/frontend/serverless/survey_banner_spec.js b/spec/frontend/serverless/survey_banner_spec.js
index 29b36fb9b5f..4682c2328c3 100644
--- a/spec/frontend/serverless/survey_banner_spec.js
+++ b/spec/frontend/serverless/survey_banner_spec.js
@@ -1,6 +1,6 @@
+import { GlBanner } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
-import { GlBanner } from '@gitlab/ui';
import SurveyBanner from '~/serverless/survey_banner.vue';
describe('Knative survey banner', () => {
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 f3085fb7ffb..21b9721438d 100644
--- a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
+++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { initEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
diff --git a/spec/frontend/set_status_modal/user_availability_status_spec.js b/spec/frontend/set_status_modal/user_availability_status_spec.js
deleted file mode 100644
index 95ca0251ce0..00000000000
--- a/spec/frontend/set_status_modal/user_availability_status_spec.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import UserAvailabilityStatus from '~/set_status_modal/components/user_availability_status.vue';
-import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
-
-describe('UserAvailabilityStatus', () => {
- let wrapper;
-
- const createComponent = (props = {}) => {
- return shallowMount(UserAvailabilityStatus, {
- propsData: {
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('with availability status', () => {
- it(`set to ${AVAILABILITY_STATUS.BUSY}`, () => {
- wrapper = createComponent({ availability: AVAILABILITY_STATUS.BUSY });
- expect(wrapper.text()).toContain('(Busy)');
- });
-
- it(`set to ${AVAILABILITY_STATUS.NOT_SET}`, () => {
- wrapper = createComponent({ availability: AVAILABILITY_STATUS.NOT_SET });
- expect(wrapper.html()).toBe('');
- });
- });
-});
diff --git a/spec/frontend/set_status_modal/utils_spec.js b/spec/frontend/set_status_modal/utils_spec.js
new file mode 100644
index 00000000000..273f30f8311
--- /dev/null
+++ b/spec/frontend/set_status_modal/utils_spec.js
@@ -0,0 +1,15 @@
+import { AVAILABILITY_STATUS, isUserBusy } from '~/set_status_modal/utils';
+
+describe('Set status modal utils', () => {
+ describe('isUserBusy', () => {
+ it.each`
+ value | result
+ ${''} | ${false}
+ ${'fake status'} | ${false}
+ ${AVAILABILITY_STATUS.NOT_SET} | ${false}
+ ${AVAILABILITY_STATUS.BUSY} | ${true}
+ `('with $value returns $result', ({ value, result }) => {
+ expect(isUserBusy(value)).toBe(result);
+ });
+ });
+});
diff --git a/spec/frontend/settings_panels_spec.js b/spec/frontend/settings_panels_spec.js
index 2c5d91a45bc..8666106d3c6 100644
--- a/spec/frontend/settings_panels_spec.js
+++ b/spec/frontend/settings_panels_spec.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import initSettingsPanels from '~/settings_panels';
+import initSettingsPanels, { isExpanded } from '~/settings_panels';
describe('Settings Panels', () => {
preloadFixtures('groups/edit.html');
@@ -20,11 +20,11 @@ describe('Settings Panels', () => {
// Our test environment automatically expands everything so we need to clear that out first
panel.classList.remove('expanded');
- expect(panel.classList.contains('expanded')).toBe(false);
+ expect(isExpanded(panel)).toBe(false);
initSettingsPanels();
- expect(panel.classList.contains('expanded')).toBe(true);
+ expect(isExpanded(panel)).toBe(true);
});
});
@@ -35,11 +35,11 @@ describe('Settings Panels', () => {
initSettingsPanels();
- expect(panel.classList.contains('expanded')).toBe(true);
+ expect(isExpanded(panel)).toBe(true);
$(trigger).click();
- expect(panel.classList.contains('expanded')).toBe(false);
+ expect(isExpanded(panel)).toBe(false);
expect(trigger.textContent).toEqual(originalText);
});
});
diff --git a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
index e295c587d70..846f45345e7 100644
--- a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
+++ b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
@@ -3,7 +3,7 @@
exports[`SidebarTodo template renders component container element with proper data attributes 1`] = `
<button
aria-label="Mark as done"
- class="btn btn-default btn-todo issuable-header-btn float-right"
+ class="gl-button btn btn-default btn-todo issuable-header-btn float-right"
data-issuable-id="1"
data-issuable-type="epic"
type="button"
diff --git a/spec/frontend/sidebar/assignee_title_spec.js b/spec/frontend/sidebar/assignee_title_spec.js
index 9f5d51c7795..3079cb28406 100644
--- a/spec/frontend/sidebar/assignee_title_spec.js
+++ b/spec/frontend/sidebar/assignee_title_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import Component from '~/sidebar/components/assignees/assignee_title.vue';
diff --git a/spec/frontend/sidebar/assignees_realtime_spec.js b/spec/frontend/sidebar/assignees_realtime_spec.js
index 1c62c52dc67..0fab6a29f71 100644
--- a/spec/frontend/sidebar/assignees_realtime_spec.js
+++ b/spec/frontend/sidebar/assignees_realtime_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import ActionCable from '@rails/actioncable';
+import { shallowMount } from '@vue/test-utils';
+import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import Mock from './mock_data';
-import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
jest.mock('@rails/actioncable', () => {
const mockConsumer = {
diff --git a/spec/frontend/sidebar/assignees_spec.js b/spec/frontend/sidebar/assignees_spec.js
index 23e82171fe9..74dce499999 100644
--- a/spec/frontend/sidebar/assignees_spec.js
+++ b/spec/frontend/sidebar/assignees_spec.js
@@ -1,6 +1,6 @@
+import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import { GlIcon } from '@gitlab/ui';
import UsersMockHelper from 'helpers/user_mock_data_helper';
import Assignee from '~/sidebar/components/assignees/assignees.vue';
import UsersMock from './mock_data';
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 03d1ac3ab8d..5a3a152d201 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
-import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
import userDataMock from '../../user_data_mock';
const TOOLTIP_PLACEMENT = 'bottom';
@@ -79,4 +79,34 @@ describe('AssigneeAvatarLink component', () => {
});
},
);
+
+ describe.each`
+ tooltipHasName | availability | canMerge | expected
+ ${true} | ${'Busy'} | ${false} | ${'Root (Busy) (cannot merge)'}
+ ${true} | ${'Busy'} | ${true} | ${'Root (Busy)'}
+ ${true} | ${''} | ${false} | ${'Root (cannot merge)'}
+ ${true} | ${''} | ${true} | ${'Root'}
+ ${false} | ${'Busy'} | ${false} | ${'Cannot merge'}
+ ${false} | ${'Busy'} | ${true} | ${''}
+ ${false} | ${''} | ${false} | ${'Cannot merge'}
+ ${false} | ${''} | ${true} | ${''}
+ `(
+ "with tooltipHasName=$tooltipHasName and availability='$availability' and canMerge=$canMerge",
+ ({ tooltipHasName, availability, canMerge, expected }) => {
+ beforeEach(() => {
+ createComponent({
+ tooltipHasName,
+ user: {
+ ...userDataMock(),
+ can_merge: canMerge,
+ availability,
+ },
+ });
+ });
+
+ it('sets tooltip to $expected', () => {
+ expect(findTooltipText()).toBe(expected);
+ });
+ },
+ );
});
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 8e158c99971..5aa8264b98c 100644
--- a/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UsersMockHelper from 'helpers/user_mock_data_helper';
-import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
+import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
const DEFAULT_MAX_COUNTER = 99;
@@ -187,4 +187,26 @@ describe('CollapsedAssigneeList component', () => {
expect(findAvatarCounter().text()).toEqual(`${DEFAULT_MAX_COUNTER}+`);
});
});
+
+ const [busyUser] = UsersMockHelper.createNumberRandomUsers(1);
+ const [canMergeUser] = UsersMockHelper.createNumberRandomUsers(1);
+ busyUser.availability = 'busy';
+ canMergeUser.can_merge = true;
+
+ describe.each`
+ users | busy | canMerge | expected
+ ${[busyUser, canMergeUser]} | ${1} | ${1} | ${`${busyUser.name} (Busy), ${canMergeUser.name} (1/2 can merge)`}
+ ${[busyUser]} | ${1} | ${0} | ${`${busyUser.name} (Busy) (cannot merge)`}
+ ${[canMergeUser]} | ${0} | ${1} | ${`${canMergeUser.name}`}
+ ${[]} | ${0} | ${0} | ${'Assignee(s)'}
+ `(
+ 'with $users.length users, $busy is busy and $canMerge that can merge',
+ ({ users, expected }) => {
+ it('generates the tooltip text', () => {
+ createComponent({ users });
+
+ expect(getTooltipTitle()).toEqual(expected);
+ });
+ },
+ );
});
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
index ee1f8ed8d2b..b49e6255923 100644
--- a/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
+import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
import userDataMock from '../../user_data_mock';
const TEST_USER = userDataMock();
@@ -18,6 +19,9 @@ describe('CollapsedAssignee assignee component', () => {
wrapper = shallowMount(CollapsedAssignee, {
propsData,
+ stubs: {
+ UserNameWithStatus,
+ },
});
}
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
new file mode 100644
index 00000000000..4ee12838491
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
@@ -0,0 +1,120 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
+
+describe('boards sidebar remove issue', () => {
+ let wrapper;
+
+ const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findEditButton = () => wrapper.find('[data-testid="edit-button"]');
+ const findTitle = () => wrapper.find('[data-testid="title"]');
+ const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
+ const findExpanded = () => wrapper.find('[data-testid="expanded-content"]');
+
+ const createComponent = ({ props = {}, slots = {}, canUpdate = false } = {}) => {
+ wrapper = shallowMount(SidebarEditableItem, {
+ attachTo: document.body,
+ provide: { canUpdate },
+ propsData: props,
+ slots,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('template', () => {
+ it('renders title', () => {
+ const title = 'Sidebar item title';
+ createComponent({ props: { title } });
+
+ expect(findTitle().text()).toBe(title);
+ });
+
+ it('hides edit button, loader and expanded content by default', () => {
+ createComponent();
+
+ expect(findEditButton().exists()).toBe(false);
+ expect(findLoader().exists()).toBe(false);
+ expect(findExpanded().isVisible()).toBe(false);
+ });
+
+ it('shows "None" if empty collapsed slot', () => {
+ createComponent();
+
+ expect(findCollapsed().text()).toBe('None');
+ });
+
+ it('renders collapsed content by default', () => {
+ const slots = { collapsed: '<div>Collapsed content</div>' };
+ createComponent({ slots });
+
+ expect(findCollapsed().text()).toBe('Collapsed content');
+ });
+
+ it('shows edit button if can update', () => {
+ createComponent({ canUpdate: true });
+
+ expect(findEditButton().exists()).toBe(true);
+ });
+
+ it('shows loading icon if loading', () => {
+ createComponent({ props: { loading: true } });
+
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('shows expanded content and hides collapsed content when clicking edit button', async () => {
+ const slots = { default: '<div>Select item</div>' };
+ createComponent({ canUpdate: true, slots });
+ findEditButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick;
+
+ expect(findCollapsed().isVisible()).toBe(false);
+ expect(findExpanded().isVisible()).toBe(true);
+ });
+ });
+
+ describe('collapsing an item by offclicking', () => {
+ beforeEach(async () => {
+ createComponent({ canUpdate: true });
+ findEditButton().vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ });
+
+ it('hides expanded section and displays collapsed section', async () => {
+ expect(findExpanded().isVisible()).toBe(true);
+ document.body.click();
+
+ await wrapper.vm.$nextTick();
+
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findExpanded().isVisible()).toBe(false);
+ });
+ });
+
+ it('emits open when edit button is clicked and edit is initailized to false', async () => {
+ createComponent({ canUpdate: true });
+
+ findEditButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.emitted().open.length).toBe(1);
+ });
+
+ it('does not emits events when collapsing with false `emitEvent`', async () => {
+ createComponent({ canUpdate: true });
+
+ findEditButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ wrapper.vm.collapse({ emitEvent: false });
+
+ expect(wrapper.emitted().close).toBeUndefined();
+ });
+});
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 da69f56d442..7e81df1d7d2 100644
--- a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import UsersMockHelper from 'helpers/user_mock_data_helper';
-import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
+import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
import userDataMock from '../../user_data_mock';
const DEFAULT_RENDER_COUNT = 5;
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
new file mode 100644
index 00000000000..9483c6624c5
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
@@ -0,0 +1,51 @@
+import { GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
+
+const name = 'Goku';
+const containerClasses = 'gl-cool-class gl-over-9000';
+
+describe('UserNameWithStatus', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ return shallowMount(UserNameWithStatus, {
+ propsData: { name, containerClasses, ...props },
+ stubs: {
+ GlSprintf,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('will render the users name', () => {
+ expect(wrapper.html()).toContain(name);
+ });
+
+ it('will not render "Busy"', () => {
+ expect(wrapper.html()).not.toContain('Busy');
+ });
+
+ it('will render all relevant containerClasses', () => {
+ const classes = wrapper.find('span').classes().join(' ');
+ expect(classes).toBe(containerClasses);
+ });
+
+ describe(`with availability="${AVAILABILITY_STATUS.BUSY}"`, () => {
+ beforeEach(() => {
+ wrapper = createComponent({ availability: AVAILABILITY_STATUS.BUSY });
+ });
+
+ it('will render "Busy"', () => {
+ expect(wrapper.html()).toContain('Goku (Busy)');
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/copy_email_to_clipboard_spec.js b/spec/frontend/sidebar/components/copy_email_to_clipboard_spec.js
index b9132fa4450..704847f65bf 100644
--- a/spec/frontend/sidebar/components/copy_email_to_clipboard_spec.js
+++ b/spec/frontend/sidebar/components/copy_email_to_clipboard_spec.js
@@ -1,7 +1,7 @@
-import { mount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { mount } from '@vue/test-utils';
import CopyEmailToClipboard from '~/sidebar/components/copy_email_to_clipboard.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('CopyEmailToClipboard component', () => {
const sampleEmail = 'sample+email@test.com';
diff --git a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
new file mode 100644
index 00000000000..7c67149b517
--- /dev/null
+++ b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
@@ -0,0 +1,91 @@
+import { shallowMount } from '@vue/test-utils';
+import { TEST_HOST } from 'helpers/test_constants';
+import ReviewerAvatarLink from '~/sidebar/components/reviewers/reviewer_avatar_link.vue';
+import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue';
+import userDataMock from '../../user_data_mock';
+
+describe('UncollapsedReviewerList component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ users: [],
+ rootPath: TEST_HOST,
+ ...props,
+ };
+
+ wrapper = shallowMount(UncollapsedReviewerList, {
+ propsData,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('single reviewer', () => {
+ beforeEach(() => {
+ const user = userDataMock();
+
+ createComponent({
+ users: [user],
+ });
+ });
+
+ it('only has one user', () => {
+ expect(wrapper.findAll(ReviewerAvatarLink).length).toBe(1);
+ });
+
+ it('shows one user with avatar, username and author name', () => {
+ expect(wrapper.text()).toContain(`@root`);
+ });
+
+ it('renders re-request loading icon', async () => {
+ await wrapper.setData({ loadingStates: { 1: 'loading' } });
+
+ expect(wrapper.find('[data-testid="re-request-button"]').props('loading')).toBe(true);
+ });
+
+ it('renders re-request success icon', async () => {
+ await wrapper.setData({ loadingStates: { 1: 'success' } });
+
+ expect(wrapper.find('[data-testid="re-request-success"]').exists()).toBe(true);
+ });
+ });
+
+ describe('multiple reviewers', () => {
+ beforeEach(() => {
+ const user = userDataMock();
+
+ createComponent({
+ users: [user, { ...user, id: 2, username: 'hello-world' }],
+ });
+ });
+
+ it('only has one user', () => {
+ expect(wrapper.findAll(ReviewerAvatarLink).length).toBe(2);
+ });
+
+ it('shows one user with avatar, username and author name', () => {
+ expect(wrapper.text()).toContain(`@root`);
+ expect(wrapper.text()).toContain(`@hello-world`);
+ });
+
+ it('renders re-request loading icon', async () => {
+ await wrapper.setData({ loadingStates: { 2: 'loading' } });
+
+ expect(wrapper.findAll('[data-testid="re-request-button"]').length).toBe(2);
+ expect(wrapper.findAll('[data-testid="re-request-button"]').at(1).props('loading')).toBe(
+ true,
+ );
+ });
+
+ it('renders re-request success icon', async () => {
+ await wrapper.setData({ loadingStates: { 2: 'success' } });
+
+ expect(wrapper.findAll('[data-testid="re-request-button"]').length).toBe(1);
+ expect(wrapper.findAll('[data-testid="re-request-success"]').length).toBe(1);
+ expect(wrapper.find('[data-testid="re-request-success"]').exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/severity/severity_spec.js b/spec/frontend/sidebar/components/severity/severity_spec.js
index ba7cea0919c..1e4624e4dcd 100644
--- a/spec/frontend/sidebar/components/severity/severity_spec.js
+++ b/spec/frontend/sidebar/components/severity/severity_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
-import SeverityToken from '~/sidebar/components/severity/severity.vue';
+import { shallowMount } from '@vue/test-utils';
import { INCIDENT_SEVERITY } from '~/sidebar/components/severity/constants';
+import SeverityToken from '~/sidebar/components/severity/severity.vue';
describe('SeverityToken', () => {
let wrapper;
diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
index fa40d75d4e9..747d370e1cf 100644
--- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
+++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
-import SidebarSeverity from '~/sidebar/components/severity/sidebar_severity.vue';
-import SeverityToken from '~/sidebar/components/severity/severity.vue';
-import updateIssuableSeverity from '~/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql';
import { INCIDENT_SEVERITY, ISSUABLE_TYPES } from '~/sidebar/components/severity/constants';
+import updateIssuableSeverity from '~/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql';
+import SeverityToken from '~/sidebar/components/severity/severity.vue';
+import SidebarSeverity from '~/sidebar/components/severity/sidebar_severity.vue';
jest.mock('~/flash');
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 0b6a2e6ceb9..4d03aedf1be 100644
--- a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
@@ -1,6 +1,6 @@
-import { createMockDirective } from 'helpers/vue_mock_directive';
import { mount } from '@vue/test-utils';
import { stubTransition } from 'helpers/stub_transition';
+import { createMockDirective } from 'helpers/vue_mock_directive';
import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
describe('Issuable Time Tracker', () => {
diff --git a/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js b/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
index 8c868205295..427e3a89c29 100644
--- a/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
+import { deprecatedCreateFlash as flash } from '~/flash';
+import createStore from '~/notes/stores';
import EditFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
import eventHub from '~/sidebar/event_hub';
-import createStore from '~/notes/stores';
-import { deprecatedCreateFlash as flash } from '~/flash';
jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
jest.mock('~/flash');
diff --git a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
index bc2df9305d0..93a6401b1fc 100644
--- a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
-import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
-import ConfidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
-import EditForm from '~/sidebar/components/confidential/edit_form.vue';
+import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import createStore from '~/notes/stores';
import * as types from '~/notes/stores/mutation_types';
+import ConfidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
+import EditForm from '~/sidebar/components/confidential/edit_form.vue';
jest.mock('~/flash');
jest.mock('~/sidebar/services/sidebar_service');
diff --git a/spec/frontend/sidebar/lock/edit_form_buttons_spec.js b/spec/frontend/sidebar/lock/edit_form_buttons_spec.js
index a14ca711204..49283ea99cf 100644
--- a/spec/frontend/sidebar/lock/edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/lock/edit_form_buttons_spec.js
@@ -1,9 +1,9 @@
import { mount } from '@vue/test-utils';
-import EditFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue';
-import eventHub from '~/sidebar/event_hub';
import { deprecatedCreateFlash as flash } from '~/flash';
-import createStore from '~/notes/stores';
import { createStore as createMrStore } from '~/mr_notes/stores';
+import createStore from '~/notes/stores';
+import EditFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue';
+import eventHub from '~/sidebar/event_hub';
import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants';
jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
diff --git a/spec/frontend/sidebar/lock/issuable_lock_form_spec.js b/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
index 92cdba4f1f2..1743e114bb0 100644
--- a/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
+++ b/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
@@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import IssuableLockForm from '~/sidebar/components/lock/issuable_lock_form.vue';
-import EditForm from '~/sidebar/components/lock/edit_form.vue';
-import createStore from '~/notes/stores';
import { createStore as createMrStore } from '~/mr_notes/stores';
+import createStore from '~/notes/stores';
+import EditForm from '~/sidebar/components/lock/edit_form.vue';
+import IssuableLockForm from '~/sidebar/components/lock/issuable_lock_form.vue';
import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants';
describe('IssuableLockForm', () => {
diff --git a/spec/frontend/sidebar/reviewer_title_spec.js b/spec/frontend/sidebar/reviewer_title_spec.js
index cbd36040579..3c250be5d5e 100644
--- a/spec/frontend/sidebar/reviewer_title_spec.js
+++ b/spec/frontend/sidebar/reviewer_title_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import Component from '~/sidebar/components/reviewers/reviewer_title.vue';
diff --git a/spec/frontend/sidebar/reviewers_spec.js b/spec/frontend/sidebar/reviewers_spec.js
index 91f28e85f3b..fc24b51287f 100644
--- a/spec/frontend/sidebar/reviewers_spec.js
+++ b/spec/frontend/sidebar/reviewers_spec.js
@@ -1,6 +1,6 @@
+import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import { GlIcon } from '@gitlab/ui';
import UsersMockHelper from 'helpers/user_mock_data_helper';
import Reviewer from '~/sidebar/components/reviewers/reviewers.vue';
import UsersMock from './mock_data';
@@ -114,8 +114,7 @@ describe('Reviewer component', () => {
editable: true,
});
- expect(wrapper.findAll('.user-item').length).toBe(users.length);
- expect(wrapper.find('.user-list-more').exists()).toBe(false);
+ expect(wrapper.findAll('[data-testid="reviewer"]').length).toBe(users.length);
});
it('shows sorted reviewer where "can merge" users are sorted first', () => {
@@ -144,10 +143,10 @@ describe('Reviewer component', () => {
users,
});
- const userItems = wrapper.findAll('.user-list .user-item a');
+ const userItems = wrapper.findAll('[data-testid="reviewer"]');
expect(userItems.length).toBe(3);
- expect(userItems.at(0).attributes('title')).toBe(users[2].name);
+ expect(userItems.at(0).find('a').attributes('title')).toBe(users[2].name);
});
it('passes the sorted reviewers to the collapsed-reviewer-list', () => {
diff --git a/spec/frontend/sidebar/sidebar_assignees_spec.js b/spec/frontend/sidebar/sidebar_assignees_spec.js
index f1c13a5f818..e737b57e33d 100644
--- a/spec/frontend/sidebar/sidebar_assignees_spec.js
+++ b/spec/frontend/sidebar/sidebar_assignees_spec.js
@@ -1,11 +1,11 @@
import { shallowMount } from '@vue/test-utils';
-import AxiosMockAdapter from 'axios-mock-adapter';
import axios from 'axios';
-import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
+import AxiosMockAdapter from 'axios-mock-adapter';
import Assigness from '~/sidebar/components/assignees/assignees.vue';
import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
-import SidebarMediator from '~/sidebar/sidebar_mediator';
+import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
import SidebarService from '~/sidebar/services/sidebar_service';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
import Mock from './mock_data';
diff --git a/spec/frontend/sidebar/sidebar_move_issue_spec.js b/spec/frontend/sidebar/sidebar_move_issue_spec.js
index 24bb5a8e916..6a7758ace40 100644
--- a/spec/frontend/sidebar/sidebar_move_issue_spec.js
+++ b/spec/frontend/sidebar/sidebar_move_issue_spec.js
@@ -1,10 +1,10 @@
-import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
+import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
+import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
-import SidebarService from '~/sidebar/services/sidebar_service';
-import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
import Mock from './mock_data';
describe('SidebarMoveIssue', () => {
diff --git a/spec/frontend/sidebar/sidebar_subscriptions_spec.js b/spec/frontend/sidebar/sidebar_subscriptions_spec.js
index 18aaeabe3dd..d900fde7e70 100644
--- a/spec/frontend/sidebar/sidebar_subscriptions_spec.js
+++ b/spec/frontend/sidebar/sidebar_subscriptions_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import SidebarSubscriptions from '~/sidebar/components/subscriptions/sidebar_subscriptions.vue';
-import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
import Mock from './mock_data';
diff --git a/spec/frontend/sidebar/subscriptions_spec.js b/spec/frontend/sidebar/subscriptions_spec.js
index 043ffd972da..e7ae59e26cf 100644
--- a/spec/frontend/sidebar/subscriptions_spec.js
+++ b/spec/frontend/sidebar/subscriptions_spec.js
@@ -1,17 +1,20 @@
+import { GlToggle } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
-import ToggleButton from '~/vue_shared/components/toggle_button.vue';
describe('Subscriptions', () => {
let wrapper;
- const findToggleButton = () => wrapper.find(ToggleButton);
+ const findToggleButton = () => wrapper.findComponent(GlToggle);
const mountComponent = (propsData) =>
- shallowMount(Subscriptions, {
- propsData,
- });
+ extendedWrapper(
+ shallowMount(Subscriptions, {
+ propsData,
+ }),
+ );
afterEach(() => {
wrapper.destroy();
@@ -24,7 +27,7 @@ describe('Subscriptions', () => {
subscribed: undefined,
});
- expect(findToggleButton().attributes('isloading')).toBe('true');
+ expect(findToggleButton().props('isLoading')).toBe(true);
});
it('is toggled "off" when currently not subscribed', () => {
@@ -32,7 +35,7 @@ describe('Subscriptions', () => {
subscribed: false,
});
- expect(findToggleButton().attributes('value')).toBeFalsy();
+ expect(findToggleButton().props('value')).toBe(false);
});
it('is toggled "on" when currently subscribed', () => {
@@ -40,7 +43,7 @@ describe('Subscriptions', () => {
subscribed: true,
});
- expect(findToggleButton().attributes('value')).toBe('true');
+ expect(findToggleButton().props('value')).toBe(true);
});
it('toggleSubscription method emits `toggleSubscription` event on eventHub and Component', () => {
@@ -93,14 +96,16 @@ describe('Subscriptions', () => {
});
it('sets the correct display text', () => {
- expect(wrapper.find('.issuable-header-text').text()).toContain(subscribeDisabledDescription);
+ expect(wrapper.findByTestId('subscription-title').text()).toContain(
+ subscribeDisabledDescription,
+ );
expect(wrapper.find({ ref: 'tooltip' }).attributes('title')).toBe(
subscribeDisabledDescription,
);
});
it('does not render the toggle button', () => {
- expect(wrapper.find('.js-issuable-subscribe-button').exists()).toBe(false);
+ expect(findToggleButton().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/sidebar/todo_spec.js b/spec/frontend/sidebar/todo_spec.js
index 4adfaf7ad7b..ff6da3abad0 100644
--- a/spec/frontend/sidebar/todo_spec.js
+++ b/spec/frontend/sidebar/todo_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import SidebarTodos from '~/sidebar/components/todo_toggle/todo.vue';
@@ -26,7 +26,7 @@ describe('SidebarTodo', () => {
it.each`
state | classes
- ${false} | ${['btn', 'btn-default', 'btn-todo', 'issuable-header-btn', 'float-right']}
+ ${false} | ${['gl-button', 'btn', 'btn-default', 'btn-todo', 'issuable-header-btn', 'float-right']}
${true} | ${['btn-blank', 'btn-todo', 'sidebar-collapsed-icon', 'dont-change-state']}
`('returns todo button classes for when `collapsed` prop is `$state`', ({ state, classes }) => {
createComponent({ collapsed: state });
@@ -35,7 +35,7 @@ describe('SidebarTodo', () => {
it.each`
isTodo | iconClass | label | icon
- ${false} | ${''} | ${'Add a To-Do'} | ${'todo-add'}
+ ${false} | ${''} | ${'Add a to do'} | ${'todo-add'}
${true} | ${'todo-undone'} | ${'Mark as done'} | ${'todo-done'}
`(
'renders proper button when `isTodo` prop is `$isTodo`',
diff --git a/spec/frontend/sidebar/user_data_mock.js b/spec/frontend/sidebar/user_data_mock.js
index df90a65f6f9..41d0331f34a 100644
--- a/spec/frontend/sidebar/user_data_mock.js
+++ b/spec/frontend/sidebar/user_data_mock.js
@@ -8,4 +8,6 @@ export default () => ({
username: 'root',
web_url: `${TEST_HOST}/root`,
can_merge: true,
+ can_update_merge_request: true,
+ reviewed: true,
});
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index b818f98efb1..2b6d3ca8c2a 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -1,138 +1,120 @@
-import VueApollo, { ApolloMutation } from 'vue-apollo';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import waitForPromises from 'helpers/wait_for_promises';
+import { merge } from 'lodash';
+import { nextTick } from 'vue';
+import VueApollo, { ApolloMutation } from 'vue-apollo';
+import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { stubComponent } from 'helpers/stub_component';
+import waitForPromises from 'helpers/wait_for_promises';
import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql';
+import CaptchaModal from '~/captcha/captcha_modal.vue';
import { deprecatedCreateFlash as Flash } from '~/flash';
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';
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
-import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
-import TitleField from '~/vue_shared/components/form/title.vue';
-import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import {
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_VISIBILITY_INTERNAL,
SNIPPET_VISIBILITY_PUBLIC,
} from '~/snippets/constants';
-import UpdateSnippetMutation from '~/snippets/mutations/updateSnippet.mutation.graphql';
import CreateSnippetMutation from '~/snippets/mutations/createSnippet.mutation.graphql';
-import { testEntries } from '../test_utils';
+import UpdateSnippetMutation from '~/snippets/mutations/updateSnippet.mutation.graphql';
+import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
+import TitleField from '~/vue_shared/components/form/title.vue';
+import { testEntries, createGQLSnippetsQueryResponse, createGQLSnippet } from '../test_utils';
jest.mock('~/flash');
const TEST_UPLOADED_FILES = ['foo/bar.txt', 'alpha/beta.js'];
-const TEST_API_ERROR = 'Ufff';
-const TEST_MUTATION_ERROR = 'Bummer';
-
+const TEST_API_ERROR = new Error('TEST_API_ERROR');
+const TEST_MUTATION_ERROR = 'Test mutation error';
+const TEST_CAPTCHA_RESPONSE = 'i-got-a-captcha';
+const TEST_CAPTCHA_SITE_KEY = 'abc123';
const TEST_ACTIONS = {
- NO_CONTENT: {
- ...testEntries.created.diff,
- content: '',
- },
- NO_PATH: {
- ...testEntries.created.diff,
- filePath: '',
- },
- VALID: {
- ...testEntries.created.diff,
- },
+ NO_CONTENT: merge({}, testEntries.created.diff, { content: '' }),
+ NO_PATH: merge({}, testEntries.created.diff, { filePath: '' }),
+ VALID: merge({}, testEntries.created.diff),
};
-
const TEST_WEB_URL = '/snippets/7';
+const TEST_SNIPPET_GID = 'gid://gitlab/PersonalSnippet/42';
+
+const createSnippet = () =>
+ merge(createGQLSnippet(), {
+ webUrl: TEST_WEB_URL,
+ visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
+ });
+
+const createQueryResponse = (obj = {}) =>
+ createGQLSnippetsQueryResponse([merge(createSnippet(), obj)]);
+
+const createMutationResponse = (key, obj = {}) => ({
+ data: {
+ [key]: merge(
+ {
+ errors: [],
+ snippet: {
+ __typename: 'Snippet',
+ webUrl: TEST_WEB_URL,
+ },
+ spamLogId: null,
+ needsCaptchaResponse: false,
+ captchaSiteKey: null,
+ },
+ obj,
+ ),
+ },
+});
+
+const createMutationResponseWithErrors = (key) =>
+ createMutationResponse(key, { errors: [TEST_MUTATION_ERROR] });
+
+const createMutationResponseWithRecaptcha = (key) =>
+ createMutationResponse(key, {
+ errors: ['ignored captcha error message'],
+ needsCaptchaResponse: true,
+ captchaSiteKey: TEST_CAPTCHA_SITE_KEY,
+ });
-const createTestSnippet = () => ({
- webUrl: TEST_WEB_URL,
- id: 7,
- title: 'Snippet Title',
- description: 'Lorem ipsum snippet desc',
- visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
+const getApiData = ({
+ id,
+ title = '',
+ description = '',
+ visibilityLevel = SNIPPET_VISIBILITY_PRIVATE,
+} = {}) => ({
+ id,
+ title,
+ description,
+ visibilityLevel,
+ blobActions: [],
});
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
describe('Snippet Edit app', () => {
+ useFakeDate();
+
let wrapper;
- let fakeApollo;
+ let getSpy;
+
+ // Mutate spy receives a "key" so that we can:
+ // - Use the same spy whether we are creating or updating.
+ // - Build the correct response object
+ // - Assert which mutation was sent
+ let mutateSpy;
+
const relativeUrlRoot = '/foo/';
const originalRelativeUrlRoot = gon.relative_url_root;
- const GetSnippetQuerySpy = jest.fn().mockResolvedValue({
- data: { snippets: { nodes: [createTestSnippet()] } },
- });
- const mutationTypes = {
- RESOLVE: jest.fn().mockResolvedValue({
- data: {
- updateSnippet: {
- errors: [],
- snippet: createTestSnippet(),
- },
- },
- }),
- RESOLVE_WITH_ERRORS: jest.fn().mockResolvedValue({
- data: {
- updateSnippet: {
- errors: [TEST_MUTATION_ERROR],
- snippet: createTestSnippet(),
- },
- createSnippet: {
- errors: [TEST_MUTATION_ERROR],
- snippet: null,
- },
- },
- }),
- REJECT: jest.fn().mockRejectedValue(TEST_API_ERROR),
- };
-
- function createComponent({
- props = {},
- loading = false,
- mutationRes = mutationTypes.RESOLVE,
- selectedLevel = SNIPPET_VISIBILITY_PRIVATE,
- withApollo = false,
- } = {}) {
- let componentData = {
- mocks: {
- $apollo: {
- queries: {
- snippet: { loading },
- },
- mutate: mutationRes,
- },
- },
- };
-
- if (withApollo) {
- const localVue = createLocalVue();
- localVue.use(VueApollo);
-
- const requestHandlers = [[GetSnippetQuery, GetSnippetQuerySpy]];
- fakeApollo = createMockApollo(requestHandlers);
- componentData = {
- localVue,
- apolloProvider: fakeApollo,
- };
- }
+ beforeEach(() => {
+ getSpy = jest.fn().mockResolvedValue(createQueryResponse());
- wrapper = shallowMount(SnippetEditApp, {
- ...componentData,
- stubs: {
- ApolloMutation,
- FormFooterActions,
- },
- provide: {
- selectedLevel,
- },
- propsData: {
- snippetGid: 'gid://gitlab/PersonalSnippet/42',
- markdownPreviewPath: 'http://preview.foo.bar',
- markdownDocsPath: 'http://docs.foo.bar',
- ...props,
- },
- });
- }
+ // See `mutateSpy` declaration comment for why we send a key
+ mutateSpy = jest.fn().mockImplementation((key) => Promise.resolve(createMutationResponse(key)));
- beforeEach(() => {
gon.relative_url_root = relativeUrlRoot;
jest.spyOn(urlUtils, 'redirectTo').mockImplementation();
});
@@ -144,10 +126,10 @@ describe('Snippet Edit app', () => {
});
const findBlobActions = () => wrapper.find(SnippetBlobActionsEdit);
+ const findCaptchaModal = () => wrapper.find(CaptchaModal);
const findSubmitButton = () => wrapper.find('[data-testid="snippet-submit-btn"]');
const findCancelButton = () => wrapper.find('[data-testid="snippet-cancel-btn"]');
const hasDisabledSubmit = () => Boolean(findSubmitButton().attributes('disabled'));
-
const clickSubmitBtn = () => wrapper.find('[data-testid="snippet-edit-form"]').trigger('submit');
const triggerBlobActions = (actions) => findBlobActions().vm.$emit('actions', actions);
const setUploadFilesHtml = (paths) => {
@@ -155,53 +137,92 @@ describe('Snippet Edit app', () => {
.map((path) => `<input name="files[]" value="${path}">`)
.join('');
};
- const getApiData = ({
- id,
- title = '',
- description = '',
- visibilityLevel = SNIPPET_VISIBILITY_PRIVATE,
- } = {}) => ({
- id,
- title,
- description,
- visibilityLevel,
- blobActions: [],
- });
+ const setTitle = (val) => wrapper.find(TitleField).vm.$emit('input', val);
+ const setDescription = (val) => wrapper.find(SnippetDescriptionEdit).vm.$emit('input', val);
- // Ideally we wouldn't call this method directly, but we don't have a way to trigger
- // apollo responses yet.
- const loadSnippet = (...nodes) => {
- if (nodes.length) {
- wrapper.setData({
- snippet: nodes[0],
- newSnippet: false,
- });
- } else {
- wrapper.setData({
- newSnippet: true,
- });
+ const createComponent = ({ props = {}, selectedLevel = SNIPPET_VISIBILITY_PRIVATE } = {}) => {
+ if (wrapper) {
+ throw new Error('wrapper already created');
}
+
+ const requestHandlers = [
+ [GetSnippetQuery, getSpy],
+ // See `mutateSpy` declaration comment for why we send a key
+ [UpdateSnippetMutation, (...args) => mutateSpy('updateSnippet', ...args)],
+ [CreateSnippetMutation, (...args) => mutateSpy('createSnippet', ...args)],
+ ];
+ const apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMount(SnippetEditApp, {
+ apolloProvider,
+ localVue,
+ stubs: {
+ ApolloMutation,
+ FormFooterActions,
+ CaptchaModal: stubComponent(CaptchaModal),
+ },
+ provide: {
+ selectedLevel,
+ },
+ propsData: {
+ snippetGid: TEST_SNIPPET_GID,
+ markdownPreviewPath: 'http://preview.foo.bar',
+ markdownDocsPath: 'http://docs.foo.bar',
+ ...props,
+ },
+ });
};
- describe('rendering', () => {
- it('renders loader while the query is in flight', () => {
- createComponent({ loading: true });
+ // Creates comopnent and waits for gql load
+ const createComponentAndLoad = async (...args) => {
+ createComponent(...args);
+
+ await waitForPromises();
+ };
+
+ // Creates loaded component and submits form
+ const createComponentAndSubmit = async (...args) => {
+ await createComponentAndLoad(...args);
+
+ clickSubmitBtn();
+
+ await waitForPromises();
+ };
+
+ describe('when loading', () => {
+ it('renders loader', () => {
+ createComponent();
+
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
+ });
- it.each([[{}], [{ snippetGid: '' }]])(
- 'should render all required components with %s',
- (props) => {
- createComponent(props);
+ describe.each`
+ snippetGid | expectedQueries
+ ${TEST_SNIPPET_GID} | ${[[{ ids: [TEST_SNIPPET_GID] }]]}
+ ${''} | ${[]}
+ `('when loaded with snippetGid=$snippetGid', ({ snippetGid, expectedQueries }) => {
+ beforeEach(() => createComponentAndLoad({ props: { snippetGid } }));
- expect(wrapper.find(TitleField).exists()).toBe(true);
- expect(wrapper.find(SnippetDescriptionEdit).exists()).toBe(true);
- expect(wrapper.find(SnippetVisibilityEdit).exists()).toBe(true);
- expect(wrapper.find(FormFooterActions).exists()).toBe(true);
- expect(findBlobActions().exists()).toBe(true);
- },
- );
+ it(`queries with ${JSON.stringify(expectedQueries)}`, () => {
+ expect(getSpy.mock.calls).toEqual(expectedQueries);
+ });
+ it('should render components', () => {
+ expect(wrapper.find(CaptchaModal).exists()).toBe(true);
+ expect(wrapper.find(TitleField).exists()).toBe(true);
+ expect(wrapper.find(SnippetDescriptionEdit).exists()).toBe(true);
+ expect(wrapper.find(SnippetVisibilityEdit).exists()).toBe(true);
+ expect(wrapper.find(FormFooterActions).exists()).toBe(true);
+ expect(findBlobActions().exists()).toBe(true);
+ });
+
+ it('should hide loader', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+ });
+
+ describe('default', () => {
it.each`
title | actions | shouldDisable
${''} | ${[]} | ${true}
@@ -211,163 +232,241 @@ describe('Snippet Edit app', () => {
${'foo'} | ${[TEST_ACTIONS.VALID, TEST_ACTIONS.NO_CONTENT]} | ${true}
${'foo'} | ${[TEST_ACTIONS.VALID, TEST_ACTIONS.NO_PATH]} | ${false}
`(
- 'should handle submit disable (title=$title, actions=$actions, shouldDisable=$shouldDisable)',
+ 'should handle submit disable (title="$title", actions="$actions", shouldDisable="$shouldDisable")',
async ({ title, actions, shouldDisable }) => {
- createComponent();
+ getSpy.mockResolvedValue(createQueryResponse({ title }));
+
+ await createComponentAndLoad();
- loadSnippet({ title });
triggerBlobActions(actions);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(hasDisabledSubmit()).toBe(shouldDisable);
},
);
it.each`
- projectPath | snippetArg | expectation
- ${''} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
- ${'project/path'} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
- ${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
- ${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
+ projectPath | snippetGid | expectation
+ ${''} | ${''} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
+ ${'project/path'} | ${''} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
+ ${''} | ${TEST_SNIPPET_GID} | ${TEST_WEB_URL}
+ ${'project/path'} | ${TEST_SNIPPET_GID} | ${TEST_WEB_URL}
`(
- 'should set cancel href when (projectPath=$projectPath, snippet=$snippetArg)',
- async ({ projectPath, snippetArg, expectation }) => {
- createComponent({
- props: { projectPath },
+ 'should set cancel href (projectPath="$projectPath", snippetGid="$snippetGid")',
+ async ({ projectPath, snippetGid, expectation }) => {
+ await createComponentAndLoad({
+ props: {
+ projectPath,
+ snippetGid,
+ },
});
- loadSnippet(...snippetArg);
-
- await wrapper.vm.$nextTick();
-
expect(findCancelButton().attributes('href')).toBe(expectation);
},
);
- });
-
- describe('functionality', () => {
- it('does not fetch snippet when create a new snippet', async () => {
- createComponent({ props: { snippetGid: '' }, withApollo: true });
-
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
- expect(GetSnippetQuerySpy).not.toHaveBeenCalled();
- });
+ it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])(
+ 'marks %s visibility by default',
+ async (visibility) => {
+ createComponent({
+ props: { snippetGid: '' },
+ selectedLevel: visibility,
+ });
- describe('default visibility', () => {
- it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])(
- 'marks %s visibility by default',
- async (visibility) => {
- createComponent({
- props: { snippetGid: '' },
- selectedLevel: visibility,
- });
- expect(wrapper.vm.snippet.visibilityLevel).toEqual(visibility);
- },
- );
- });
+ expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility);
+ },
+ );
describe('form submission handling', () => {
it.each`
- snippetArg | projectPath | uploadedFiles | input | mutation
- ${[]} | ${'project/path'} | ${[]} | ${{ ...getApiData(), projectPath: 'project/path', uploadedFiles: [] }} | ${CreateSnippetMutation}
- ${[]} | ${''} | ${[]} | ${{ ...getApiData(), projectPath: '', uploadedFiles: [] }} | ${CreateSnippetMutation}
- ${[]} | ${''} | ${TEST_UPLOADED_FILES} | ${{ ...getApiData(), projectPath: '', uploadedFiles: TEST_UPLOADED_FILES }} | ${CreateSnippetMutation}
- ${[createTestSnippet()]} | ${'project/path'} | ${[]} | ${getApiData(createTestSnippet())} | ${UpdateSnippetMutation}
- ${[createTestSnippet()]} | ${''} | ${[]} | ${getApiData(createTestSnippet())} | ${UpdateSnippetMutation}
+ snippetGid | projectPath | uploadedFiles | input | mutationType
+ ${''} | ${'project/path'} | ${[]} | ${{ ...getApiData(), projectPath: 'project/path', uploadedFiles: [] }} | ${'createSnippet'}
+ ${''} | ${''} | ${[]} | ${{ ...getApiData(), projectPath: '', uploadedFiles: [] }} | ${'createSnippet'}
+ ${''} | ${''} | ${TEST_UPLOADED_FILES} | ${{ ...getApiData(), projectPath: '', uploadedFiles: TEST_UPLOADED_FILES }} | ${'createSnippet'}
+ ${TEST_SNIPPET_GID} | ${'project/path'} | ${[]} | ${getApiData(createSnippet())} | ${'updateSnippet'}
+ ${TEST_SNIPPET_GID} | ${''} | ${[]} | ${getApiData(createSnippet())} | ${'updateSnippet'}
`(
- 'should submit mutation with (snippet=$snippetArg, projectPath=$projectPath, uploadedFiles=$uploadedFiles)',
- async ({ snippetArg, projectPath, uploadedFiles, mutation, input }) => {
- createComponent({
+ 'should submit mutation $mutationType (snippetGid=$snippetGid, projectPath=$projectPath, uploadedFiles=$uploadedFiles)',
+ async ({ snippetGid, projectPath, uploadedFiles, mutationType, input }) => {
+ await createComponentAndLoad({
props: {
+ snippetGid,
projectPath,
},
});
- loadSnippet(...snippetArg);
+
setUploadFilesHtml(uploadedFiles);
- await wrapper.vm.$nextTick();
+ await nextTick();
clickSubmitBtn();
- expect(mutationTypes.RESOLVE).toHaveBeenCalledWith({
- mutation,
- variables: {
- input,
- },
+ expect(mutateSpy).toHaveBeenCalledTimes(1);
+ expect(mutateSpy).toHaveBeenCalledWith(mutationType, {
+ input,
});
},
);
it('should redirect to snippet view on successful mutation', async () => {
- createComponent();
- loadSnippet(createTestSnippet());
-
- clickSubmitBtn();
-
- await waitForPromises();
+ await createComponentAndSubmit();
expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL);
});
it.each`
- snippetArg | projectPath | mutationRes | expectMessage
- ${[]} | ${'project/path'} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
- ${[]} | ${''} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
- ${[]} | ${''} | ${mutationTypes.REJECT} | ${`Can't create snippet: ${TEST_API_ERROR}`}
- ${[createTestSnippet()]} | ${'project/path'} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
- ${[createTestSnippet()]} | ${''} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
+ snippetGid | projectPath | mutationRes | expectMessage
+ ${''} | ${'project/path'} | ${createMutationResponseWithErrors('createSnippet')} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
+ ${''} | ${''} | ${createMutationResponseWithErrors('createSnippet')} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
+ ${TEST_SNIPPET_GID} | ${'project/path'} | ${createMutationResponseWithErrors('updateSnippet')} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
+ ${TEST_SNIPPET_GID} | ${''} | ${createMutationResponseWithErrors('updateSnippet')} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
`(
- 'should flash error with (snippet=$snippetArg, projectPath=$projectPath)',
- async ({ snippetArg, projectPath, mutationRes, expectMessage }) => {
- createComponent({
+ 'should flash error with (snippet=$snippetGid, projectPath=$projectPath)',
+ async ({ snippetGid, projectPath, mutationRes, expectMessage }) => {
+ mutateSpy.mockResolvedValue(mutationRes);
+
+ await createComponentAndSubmit({
props: {
projectPath,
+ snippetGid,
},
- mutationRes,
});
- loadSnippet(...snippetArg);
-
- clickSubmitBtn();
-
- await waitForPromises();
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
expect(Flash).toHaveBeenCalledWith(expectMessage);
},
);
- });
- describe('on before unload', () => {
- it.each`
- condition | expectPrevented | action
- ${'there are no actions'} | ${false} | ${() => triggerBlobActions([])}
- ${'there are actions'} | ${true} | ${() => triggerBlobActions([testEntries.updated.diff])}
- ${'the snippet is being saved'} | ${false} | ${() => clickSubmitBtn()}
- `(
- 'handles before unload prevent when $condition (expectPrevented=$expectPrevented)',
- ({ expectPrevented, action }) => {
- createComponent();
- loadSnippet();
+ describe('with apollo network error', () => {
+ beforeEach(async () => {
+ jest.spyOn(console, 'error').mockImplementation();
+ mutateSpy.mockRejectedValue(TEST_API_ERROR);
- action();
+ await createComponentAndSubmit();
+ });
- const event = new Event('beforeunload');
- const returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
+ it('should not redirect', () => {
+ expect(urlUtils.redirectTo).not.toHaveBeenCalled();
+ });
- window.dispatchEvent(event);
+ it('should flash', () => {
+ // Apollo automatically wraps the resolver's error in a NetworkError
+ expect(Flash).toHaveBeenCalledWith(
+ `Can't update snippet: Network error: ${TEST_API_ERROR.message}`,
+ );
+ });
- if (expectPrevented) {
- expect(returnValueSetter).toHaveBeenCalledWith(
- 'Are you sure you want to lose unsaved changes?',
- );
- } else {
- expect(returnValueSetter).not.toHaveBeenCalled();
- }
- },
- );
+ it('should console error', () => {
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledWith(
+ '[gitlab] unexpected error while updating snippet',
+ expect.objectContaining({ message: `Network error: ${TEST_API_ERROR.message}` }),
+ );
+ });
+ });
+
+ describe('when needsCaptchaResponse is true', () => {
+ let modal;
+
+ beforeEach(async () => {
+ mutateSpy
+ .mockResolvedValueOnce(createMutationResponseWithRecaptcha('updateSnippet'))
+ .mockResolvedValueOnce(createMutationResponseWithErrors('updateSnippet'));
+
+ await createComponentAndSubmit();
+
+ modal = findCaptchaModal();
+
+ mutateSpy.mockClear();
+ });
+
+ it('should display captcha modal', () => {
+ expect(urlUtils.redirectTo).not.toHaveBeenCalled();
+ expect(modal.props()).toEqual({
+ needsCaptchaResponse: true,
+ captchaSiteKey: TEST_CAPTCHA_SITE_KEY,
+ });
+ });
+
+ describe.each`
+ response | expectedCalls
+ ${null} | ${[]}
+ ${TEST_CAPTCHA_RESPONSE} | ${[['updateSnippet', { input: { ...getApiData(createSnippet()), captchaResponse: TEST_CAPTCHA_RESPONSE } }]]}
+ `('when captcha response is $response', ({ response, expectedCalls }) => {
+ beforeEach(async () => {
+ modal.vm.$emit('receivedCaptchaResponse', response);
+
+ await nextTick();
+ });
+
+ it('sets needsCaptchaResponse to false', () => {
+ expect(modal.props('needsCaptchaResponse')).toEqual(false);
+ });
+
+ it(`expected to call times = ${expectedCalls.length}`, () => {
+ expect(mutateSpy.mock.calls).toEqual(expectedCalls);
+ });
+ });
+ });
});
});
+
+ describe('on before unload', () => {
+ it.each([
+ ['there are no actions', false, () => triggerBlobActions([])],
+ ['there is an empty action', false, () => triggerBlobActions([testEntries.empty.diff])],
+ ['there are actions', true, () => triggerBlobActions([testEntries.updated.diff])],
+ [
+ 'the title is set',
+ true,
+ () => {
+ triggerBlobActions([testEntries.empty.diff]);
+ setTitle('test');
+ },
+ ],
+ [
+ 'the description is set',
+ true,
+ () => {
+ triggerBlobActions([testEntries.empty.diff]);
+ setDescription('test');
+ },
+ ],
+ [
+ 'the snippet is being saved',
+ false,
+ () => {
+ triggerBlobActions([testEntries.updated.diff]);
+ clickSubmitBtn();
+ },
+ ],
+ ])(
+ 'handles before unload prevent when %s (expectPrevented=%s)',
+ async (_, expectPrevented, action) => {
+ await createComponentAndLoad({
+ props: {
+ snippetGid: '',
+ },
+ });
+
+ action();
+
+ const event = new Event('beforeunload');
+ const returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
+
+ window.dispatchEvent(event);
+
+ if (expectPrevented) {
+ expect(returnValueSetter).toHaveBeenCalledWith(
+ 'Are you sure you want to lose unsaved changes?',
+ );
+ } else {
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ }
+ },
+ );
+ });
});
diff --git a/spec/frontend/snippets/components/embed_dropdown_spec.js b/spec/frontend/snippets/components/embed_dropdown_spec.js
index f1eb7d43409..389b1c618a3 100644
--- a/spec/frontend/snippets/components/embed_dropdown_spec.js
+++ b/spec/frontend/snippets/components/embed_dropdown_spec.js
@@ -1,6 +1,6 @@
-import { escape as esc } from 'lodash';
-import { mount } from '@vue/test-utils';
import { GlFormInputGroup } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { escape as esc } from 'lodash';
import { TEST_HOST } from 'helpers/test_constants';
import EmbedDropdown from '~/snippets/components/embed_dropdown.vue';
diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js
index b5ab7def753..e6162c6aad2 100644
--- a/spec/frontend/snippets/components/show_spec.js
+++ b/spec/frontend/snippets/components/show_spec.js
@@ -1,18 +1,17 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { shallowMount } from '@vue/test-utils';
-import SnippetApp from '~/snippets/components/show.vue';
+import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import EmbedDropdown from '~/snippets/components/embed_dropdown.vue';
+import SnippetApp from '~/snippets/components/show.vue';
+import SnippetBlob from '~/snippets/components/snippet_blob_view.vue';
import SnippetHeader from '~/snippets/components/snippet_header.vue';
import SnippetTitle from '~/snippets/components/snippet_title.vue';
-import SnippetBlob from '~/snippets/components/snippet_blob_view.vue';
-import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
-
import {
SNIPPET_VISIBILITY_INTERNAL,
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_VISIBILITY_PUBLIC,
} from '~/snippets/constants';
+import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
describe('Snippet view app', () => {
let wrapper;
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 08056e788de..2693b26aeae 100644
--- a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
@@ -1,5 +1,5 @@
-import { times } from 'lodash';
import { shallowMount } from '@vue/test-utils';
+import { times } from 'lodash';
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
import {
diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
index 9d0311fd682..a7ab205ca7b 100644
--- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
@@ -1,14 +1,14 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'helpers/test_constants';
-import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
+import waitForPromises from 'helpers/wait_for_promises';
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
-import EditorLite from '~/vue_shared/components/editor_lite.vue';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { joinPaths } from '~/lib/utils/url_utility';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
+import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
+import EditorLite from '~/vue_shared/components/editor_lite.vue';
jest.mock('~/flash');
diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js
index 1ccecd7b5ba..b92c1907980 100644
--- a/spec/frontend/snippets/components/snippet_blob_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js
@@ -1,5 +1,5 @@
-import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import {
Blob as BlobMock,
SimpleViewerMock,
@@ -7,16 +7,16 @@ import {
RichBlobContentMock,
SimpleBlobContentMock,
} from 'jest/blob/components/mock_data';
-import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue';
-import BlobHeader from '~/blob/components/blob_header.vue';
import BlobContent from '~/blob/components/blob_content.vue';
+import BlobHeader from '~/blob/components/blob_header.vue';
import {
BLOB_RENDER_EVENT_LOAD,
BLOB_RENDER_EVENT_SHOW_SOURCE,
BLOB_RENDER_ERRORS,
} from '~/blob/components/constants';
-import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
+import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue';
import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
+import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
describe('Blob Embeddable', () => {
let wrapper;
diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index 93a66db32c6..585614a6b79 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -1,11 +1,11 @@
-import { ApolloMutation } from 'vue-apollo';
import { GlButton, GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
+import { ApolloMutation } from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises';
-import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.graphql';
-import SnippetHeader from '~/snippets/components/snippet_header.vue';
+import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
+import SnippetHeader from '~/snippets/components/snippet_header.vue';
+import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.graphql';
describe('Snippet header component', () => {
let wrapper;
diff --git a/spec/frontend/snippets/components/snippet_title_spec.js b/spec/frontend/snippets/components/snippet_title_spec.js
index f201cfb19b7..48fb51ce703 100644
--- a/spec/frontend/snippets/components/snippet_title_spec.js
+++ b/spec/frontend/snippets/components/snippet_title_spec.js
@@ -1,7 +1,7 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import SnippetTitle from '~/snippets/components/snippet_title.vue';
import SnippetDescription from '~/snippets/components/snippet_description_view.vue';
+import SnippetTitle from '~/snippets/components/snippet_title.vue';
describe('Snippet header component', () => {
let wrapper;
diff --git a/spec/frontend/snippets/test_utils.js b/spec/frontend/snippets/test_utils.js
index 86262723157..8ba5a2fe5dc 100644
--- a/spec/frontend/snippets/test_utils.js
+++ b/spec/frontend/snippets/test_utils.js
@@ -1,3 +1,4 @@
+import { TEST_HOST } from 'helpers/test_constants';
import {
SNIPPET_BLOB_ACTION_CREATE,
SNIPPET_BLOB_ACTION_UPDATE,
@@ -8,6 +9,51 @@ import {
const CONTENT_1 = 'Lorem ipsum dolar\nSit amit\n\nGoodbye!\n';
const CONTENT_2 = 'Lorem ipsum dolar sit amit.\n\nGoodbye!\n';
+export const createGQLSnippet = () => ({
+ __typename: 'Snippet',
+ id: 7,
+ title: 'Snippet Title',
+ description: 'Lorem ipsum snippet desc',
+ descriptionHtml: '<p>Lorem ipsum snippet desc</p>',
+ createdAt: new Date(Date.now() - 1e6),
+ updatedAt: new Date(Date.now() - 1e3),
+ httpUrlToRepo: `${TEST_HOST}/repo`,
+ sshUrlToRepo: 'ssh://ssh.test/repo',
+ blobs: [],
+ userPermissions: {
+ __typename: 'SnippetPermissions',
+ adminSnippet: true,
+ updateSnippet: true,
+ },
+ project: {
+ __typename: 'Project',
+ fullPath: 'group/project',
+ webUrl: `${TEST_HOST}/group/project`,
+ },
+ author: {
+ __typename: 'User',
+ id: 1,
+ avatarUrl: `${TEST_HOST}/avatar.png`,
+ name: 'root',
+ username: 'root',
+ webUrl: `${TEST_HOST}/root`,
+ status: {
+ __typename: 'UserStatus',
+ emoji: '',
+ message: '',
+ },
+ },
+});
+
+export const createGQLSnippetsQueryResponse = (snippets) => ({
+ data: {
+ snippets: {
+ __typename: 'SnippetConnection',
+ nodes: snippets,
+ },
+ },
+});
+
export const testEntries = {
created: {
id: 'blob_1',
@@ -56,6 +102,15 @@ export const testEntries = {
content: CONTENT_2,
},
},
+ empty: {
+ id: 'empty',
+ diff: {
+ action: SNIPPET_BLOB_ACTION_CREATE,
+ filePath: '',
+ previousPath: '',
+ content: '',
+ },
+ },
};
export const createBlobFromTestEntry = ({ diff, origContent }, isOrig = false) => ({
diff --git a/spec/frontend/snippets/utils/error_spec.js b/spec/frontend/snippets/utils/error_spec.js
new file mode 100644
index 00000000000..385554568db
--- /dev/null
+++ b/spec/frontend/snippets/utils/error_spec.js
@@ -0,0 +1,16 @@
+import { getErrorMessage, UNEXPECTED_ERROR } from '~/snippets/utils/error';
+
+describe('~/snippets/utils/error', () => {
+ describe('getErrorMessage', () => {
+ it.each`
+ input | output
+ ${null} | ${UNEXPECTED_ERROR}
+ ${'message'} | ${'message'}
+ ${new Error('test message')} | ${'test message'}
+ ${{ networkError: 'Network error: test message' }} | ${'Network error: test message'}
+ ${{}} | ${UNEXPECTED_ERROR}
+ `('with $input, should return "$output"', ({ input, output }) => {
+ expect(getErrorMessage(input)).toBe(output);
+ });
+ });
+});
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js
index 07097d00cc5..17fb3fe788a 100644
--- a/spec/frontend/static_site_editor/components/edit_area_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_area_spec.js
@@ -1,15 +1,14 @@
-import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
-import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
-import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/constants';
-
import EditArea from '~/static_site_editor/components/edit_area.vue';
-import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
-import EditHeader from '~/static_site_editor/components/edit_header.vue';
import EditDrawer from '~/static_site_editor/components/edit_drawer.vue';
+import EditHeader from '~/static_site_editor/components/edit_header.vue';
+import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
import UnsavedChangesConfirmDialog from '~/static_site_editor/components/unsaved_changes_confirm_dialog.vue';
+import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/constants';
+import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import {
sourceContentTitle as title,
diff --git a/spec/frontend/static_site_editor/components/edit_drawer_spec.js b/spec/frontend/static_site_editor/components/edit_drawer_spec.js
index c47eef59997..402dfe441c5 100644
--- a/spec/frontend/static_site_editor/components/edit_drawer_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_drawer_spec.js
@@ -1,6 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
-
import { GlDrawer } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import EditDrawer from '~/static_site_editor/components/edit_drawer.vue';
import FrontMatterControls from '~/static_site_editor/components/front_matter_controls.vue';
diff --git a/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js b/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
index cf82efc3d0b..7a8834933e0 100644
--- a/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
@@ -1,6 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
-
import { GlDropdown, GlDropdownItem, GlFormInput, GlFormTextarea } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
diff --git a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
index c7d0abee05c..3a336f6a230 100644
--- a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
-import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import axios from '~/lib/utils/axios_utils';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
+import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
import { MR_META_LOCAL_STORAGE_KEY } from '~/static_site_editor/constants';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
sourcePath,
mergeRequestMeta,
diff --git a/spec/frontend/static_site_editor/components/front_matter_controls_spec.js b/spec/frontend/static_site_editor/components/front_matter_controls_spec.js
index 8001f2fbd29..5fda3b40306 100644
--- a/spec/frontend/static_site_editor/components/front_matter_controls_spec.js
+++ b/spec/frontend/static_site_editor/components/front_matter_controls_spec.js
@@ -1,6 +1,6 @@
+import { GlFormGroup } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlFormGroup } from '@gitlab/ui';
import { humanize } from '~/lib/utils/text_utility';
import FrontMatterControls from '~/static_site_editor/components/front_matter_controls.vue';
diff --git a/spec/frontend/static_site_editor/components/submit_changes_error_spec.js b/spec/frontend/static_site_editor/components/submit_changes_error_spec.js
index 7af3014b338..82a5c5f624a 100644
--- a/spec/frontend/static_site_editor/components/submit_changes_error_spec.js
+++ b/spec/frontend/static_site_editor/components/submit_changes_error_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import SubmitChangesError from '~/static_site_editor/components/submit_changes_error.vue';
diff --git a/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js b/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js
index 750b777cf5d..a0529f5f945 100644
--- a/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js
+++ b/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js
@@ -1,6 +1,6 @@
import savedContentMetaQuery from '~/static_site_editor/graphql/queries/saved_content_meta.query.graphql';
-import submitContentChanges from '~/static_site_editor/services/submit_content_changes';
import submitContentChangesResolver from '~/static_site_editor/graphql/resolvers/submit_content_changes';
+import submitContentChanges from '~/static_site_editor/services/submit_content_changes';
import {
projectId as project,
diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js
index 3e488a950dc..0936ba3011c 100644
--- a/spec/frontend/static_site_editor/pages/home_spec.js
+++ b/spec/frontend/static_site_editor/pages/home_spec.js
@@ -1,15 +1,15 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import Home from '~/static_site_editor/pages/home.vue';
-import SkeletonLoader from '~/static_site_editor/components/skeleton_loader.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue';
+import SkeletonLoader from '~/static_site_editor/components/skeleton_loader.vue';
import SubmitChangesError from '~/static_site_editor/components/submit_changes_error.vue';
-import submitContentChangesMutation from '~/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql';
+import { TRACKING_ACTION_INITIALIZE_EDITOR } from '~/static_site_editor/constants';
import hasSubmittedChangesMutation from '~/static_site_editor/graphql/mutations/has_submitted_changes.mutation.graphql';
+import submitContentChangesMutation from '~/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql';
+import Home from '~/static_site_editor/pages/home.vue';
import { SUCCESS_ROUTE } from '~/static_site_editor/router/constants';
-import { TRACKING_ACTION_INITIALIZE_EDITOR } from '~/static_site_editor/constants';
import {
project,
diff --git a/spec/frontend/static_site_editor/pages/success_spec.js b/spec/frontend/static_site_editor/pages/success_spec.js
index 3fc69dc4586..fbdc2c435a0 100644
--- a/spec/frontend/static_site_editor/pages/success_spec.js
+++ b/spec/frontend/static_site_editor/pages/success_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Success from '~/static_site_editor/pages/success.vue';
-import { savedContentMeta, returnUrl, sourcePath } from '../mock_data';
import { HOME_ROUTE } from '~/static_site_editor/router/constants';
+import { savedContentMeta, returnUrl, sourcePath } from '../mock_data';
describe('~/static_site_editor/pages/success.vue', () => {
const mergeRequestsIllustrationPath = 'illustrations/merge_requests.svg';
diff --git a/spec/frontend/static_site_editor/services/front_matterify_spec.js b/spec/frontend/static_site_editor/services/front_matterify_spec.js
index 866897f21ef..ec3752b30c6 100644
--- a/spec/frontend/static_site_editor/services/front_matterify_spec.js
+++ b/spec/frontend/static_site_editor/services/front_matterify_spec.js
@@ -1,3 +1,4 @@
+import { frontMatterify, stringify } from '~/static_site_editor/services/front_matterify';
import {
sourceContentYAML as content,
sourceContentHeaderObjYAML as yamlFrontMatterObj,
@@ -5,8 +6,6 @@ import {
sourceContentBody as body,
} from '../mock_data';
-import { frontMatterify, stringify } from '~/static_site_editor/services/front_matterify';
-
describe('static_site_editor/services/front_matterify', () => {
const frontMatterifiedContent = {
source: content,
diff --git a/spec/frontend/static_site_editor/services/parse_source_file_spec.js b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
index ab9e63f4cd2..fdd11297e09 100644
--- a/spec/frontend/static_site_editor/services/parse_source_file_spec.js
+++ b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
@@ -1,3 +1,4 @@
+import parseSourceFile from '~/static_site_editor/services/parse_source_file';
import {
sourceContentYAML as content,
sourceContentHeaderYAML as yamlFrontMatter,
@@ -5,8 +6,6 @@ import {
sourceContentBody as body,
} from '../mock_data';
-import parseSourceFile from '~/static_site_editor/services/parse_source_file';
-
describe('static_site_editor/services/parse_source_file', () => {
const contentComplex = [content, content, content].join('');
const complexBody = [body, content, content].join('');
diff --git a/spec/frontend/task_list_spec.js b/spec/frontend/task_list_spec.js
index a8bdc506102..b6ac3167fea 100644
--- a/spec/frontend/task_list_spec.js
+++ b/spec/frontend/task_list_spec.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
-import TaskList from '~/task_list';
import axios from '~/lib/utils/axios_utils';
+import TaskList from '~/task_list';
describe('TaskList', () => {
let taskList;
diff --git a/spec/frontend/terraform/components/states_table_actions_spec.js b/spec/frontend/terraform/components/states_table_actions_spec.js
index 3f5df8a96f8..61f6e9f0f7b 100644
--- a/spec/frontend/terraform/components/states_table_actions_spec.js
+++ b/spec/frontend/terraform/components/states_table_actions_spec.js
@@ -1,7 +1,8 @@
import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
-import createMockApollo from 'helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import StateActions from '~/terraform/components/states_table_actions.vue';
import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql';
import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql';
@@ -13,7 +14,9 @@ localVue.use(VueApollo);
describe('StatesTableActions', () => {
let lockResponse;
let removeResponse;
+ let toast;
let unlockResponse;
+ let updateStateResponse;
let wrapper;
const defaultProps = {
@@ -26,7 +29,9 @@ describe('StatesTableActions', () => {
};
const createMockApolloProvider = () => {
- lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } });
+ lockResponse = jest
+ .fn()
+ .mockResolvedValue({ data: { terraformStateLock: { errors: ['There was an error'] } } });
removeResponse = jest
.fn()
@@ -36,26 +41,39 @@ describe('StatesTableActions', () => {
.fn()
.mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } });
- return createMockApollo([
- [lockStateMutation, lockResponse],
- [removeStateMutation, removeResponse],
- [unlockStateMutation, unlockResponse],
- ]);
+ updateStateResponse = jest.fn().mockResolvedValue({});
+
+ return createMockApollo(
+ [
+ [lockStateMutation, lockResponse],
+ [removeStateMutation, removeResponse],
+ [unlockStateMutation, unlockResponse],
+ ],
+ {
+ Mutation: {
+ addDataToTerraformState: updateStateResponse,
+ },
+ },
+ );
};
const createComponent = (propsData = defaultProps) => {
const apolloProvider = createMockApolloProvider();
+ toast = jest.fn();
+
wrapper = shallowMount(StateActions, {
apolloProvider,
localVue,
propsData,
+ mocks: { $toast: { show: toast } },
stubs: { GlDropdown, GlModal, GlSprintf },
});
return wrapper.vm.$nextTick();
};
+ const findActionsDropdown = () => wrapper.find(GlDropdown);
const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]');
const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]');
const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
@@ -69,10 +87,44 @@ describe('StatesTableActions', () => {
afterEach(() => {
lockResponse = null;
removeResponse = null;
+ toast = null;
unlockResponse = null;
+ updateStateResponse = null;
wrapper.destroy();
});
+ describe('when the state is loading', () => {
+ describe('when lock/unlock is processing', () => {
+ beforeEach(() => {
+ return createComponent({
+ state: {
+ ...defaultProps.state,
+ loadingLock: true,
+ },
+ });
+ });
+
+ it('disables the actions dropdown', () => {
+ expect(findActionsDropdown().props('disabled')).toBe(true);
+ });
+ });
+
+ describe('when remove is processing', () => {
+ beforeEach(() => {
+ return createComponent({
+ state: {
+ ...defaultProps.state,
+ loadingRemove: true,
+ },
+ });
+ });
+
+ it('disables the actions dropdown', () => {
+ expect(findActionsDropdown().props('disabled')).toBe(true);
+ });
+ });
+ });
+
describe('download button', () => {
it('displays a download button', () => {
expect(findDownloadBtn().text()).toBe('Download JSON');
@@ -104,7 +156,8 @@ describe('StatesTableActions', () => {
describe('when clicking the unlock button', () => {
beforeEach(() => {
findUnlockBtn().vm.$emit('click');
- return wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('calls the unlock mutation', () => {
@@ -137,7 +190,8 @@ describe('StatesTableActions', () => {
describe('when clicking the lock button', () => {
beforeEach(() => {
findLockBtn().vm.$emit('click');
- return wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('calls the lock mutation', () => {
@@ -145,6 +199,44 @@ describe('StatesTableActions', () => {
stateID: unlockedProps.state.id,
});
});
+
+ it('calls mutations to set loading and errors', () => {
+ // loading update
+ expect(updateStateResponse).toHaveBeenNthCalledWith(
+ 1,
+ {},
+ {
+ terraformState: {
+ ...unlockedProps.state,
+ _showDetails: false,
+ errorMessages: [],
+ loadingLock: true,
+ loadingRemove: false,
+ },
+ },
+ // Apollo fields
+ expect.any(Object),
+ expect.any(Object),
+ );
+
+ // final update
+ expect(updateStateResponse).toHaveBeenNthCalledWith(
+ 2,
+ {},
+ {
+ terraformState: {
+ ...unlockedProps.state,
+ _showDetails: true,
+ errorMessages: ['There was an error'],
+ loadingLock: false,
+ loadingRemove: false,
+ },
+ },
+ // Apollo fields
+ expect.any(Object),
+ expect.any(Object),
+ );
+ });
});
});
@@ -156,7 +248,7 @@ describe('StatesTableActions', () => {
describe('when clicking the remove button', () => {
beforeEach(() => {
findRemoveBtn().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ return waitForPromises();
});
it('displays a remove modal', () => {
@@ -166,21 +258,70 @@ describe('StatesTableActions', () => {
});
describe('when submitting the remove modal', () => {
- it('does not call the remove mutation when state name is missing', async () => {
- findRemoveModal().vm.$emit('ok');
- await wrapper.vm.$nextTick();
+ describe('when state name is missing', () => {
+ beforeEach(() => {
+ findRemoveModal().vm.$emit('ok');
+ return waitForPromises();
+ });
- expect(removeResponse).not.toHaveBeenCalledWith();
+ it('does not call the remove mutation', () => {
+ expect(removeResponse).not.toHaveBeenCalledWith();
+ });
});
- it('calls the remove mutation when state name is present', async () => {
- await wrapper.setData({ removeConfirmText: defaultProps.state.name });
+ describe('when state name is present', () => {
+ beforeEach(async () => {
+ await wrapper.setData({ removeConfirmText: defaultProps.state.name });
+
+ findRemoveModal().vm.$emit('ok');
- findRemoveModal().vm.$emit('ok');
- await wrapper.vm.$nextTick();
+ await waitForPromises();
+ });
+
+ it('calls the remove mutation', () => {
+ expect(removeResponse).toHaveBeenCalledWith({ stateID: defaultProps.state.id });
+ });
+
+ it('calls the toast action', () => {
+ expect(toast).toHaveBeenCalledWith(`${defaultProps.state.name} successfully removed`);
+ });
- expect(removeResponse).toHaveBeenCalledWith({
- stateID: defaultProps.state.id,
+ it('calls mutations to set loading and errors', () => {
+ // loading update
+ expect(updateStateResponse).toHaveBeenNthCalledWith(
+ 1,
+ {},
+ {
+ terraformState: {
+ ...defaultProps.state,
+ _showDetails: false,
+ errorMessages: [],
+ loadingLock: false,
+ loadingRemove: true,
+ },
+ },
+ // Apollo fields
+ expect.any(Object),
+ expect.any(Object),
+ );
+
+ // final update
+ expect(updateStateResponse).toHaveBeenNthCalledWith(
+ 2,
+ {},
+ {
+ terraformState: {
+ ...defaultProps.state,
+ _showDetails: false,
+ errorMessages: [],
+ loadingLock: false,
+ loadingRemove: false,
+ },
+ },
+ // Apollo fields
+ expect.any(Object),
+ expect.any(Object),
+ );
});
});
});
diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js
index f2b7bc00e5b..100e577f514 100644
--- a/spec/frontend/terraform/components/states_table_spec.js
+++ b/spec/frontend/terraform/components/states_table_spec.js
@@ -1,8 +1,8 @@
-import { GlIcon, GlTooltip } from '@gitlab/ui';
+import { GlIcon, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
-import StateActions from '~/terraform/components/states_table_actions.vue';
import StatesTable from '~/terraform/components/states_table.vue';
+import StateActions from '~/terraform/components/states_table_actions.vue';
describe('StatesTable', () => {
let wrapper;
@@ -11,7 +11,11 @@ describe('StatesTable', () => {
const defaultProps = {
states: [
{
+ _showDetails: true,
+ errorMessages: ['State 1 has errored'],
name: 'state-1',
+ loadingLock: false,
+ loadingRemove: false,
lockedAt: '2020-10-13T00:00:00Z',
lockedByUser: {
name: 'user-1',
@@ -20,14 +24,22 @@ describe('StatesTable', () => {
latestVersion: null,
},
{
+ _showDetails: false,
+ errorMessages: [],
name: 'state-2',
+ loadingLock: true,
+ loadingRemove: false,
lockedAt: null,
lockedByUser: null,
updatedAt: '2020-10-10T00:00:00Z',
latestVersion: null,
},
{
+ _showDetails: false,
+ errorMessages: [],
name: 'state-3',
+ loadingLock: true,
+ loadingRemove: false,
lockedAt: '2020-10-10T00:00:00Z',
lockedByUser: {
name: 'user-2',
@@ -54,7 +66,11 @@ describe('StatesTable', () => {
},
},
{
+ _showDetails: true,
+ errorMessages: ['State 4 has errored'],
name: 'state-4',
+ loadingLock: false,
+ loadingRemove: false,
lockedAt: '2020-10-10T00:00:00Z',
lockedByUser: null,
updatedAt: '2020-10-10T00:00:00Z',
@@ -76,6 +92,17 @@ describe('StatesTable', () => {
},
},
},
+ {
+ _showDetails: false,
+ errorMessages: [],
+ name: 'state-5',
+ loadingLock: false,
+ loadingRemove: true,
+ lockedAt: null,
+ lockedByUser: null,
+ updatedAt: '2020-10-10T00:00:00Z',
+ latestVersion: null,
+ },
],
};
@@ -96,14 +123,15 @@ describe('StatesTable', () => {
});
it.each`
- name | toolTipText | locked | lineNumber
- ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0}
- ${'state-2'} | ${null} | ${false} | ${1}
- ${'state-3'} | ${'Locked by user-2 5 days ago'} | ${true} | ${2}
- ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3}
+ name | toolTipText | locked | loading | lineNumber
+ ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0}
+ ${'state-2'} | ${'Locking state'} | ${false} | ${true} | ${1}
+ ${'state-3'} | ${'Unlocking state'} | ${false} | ${true} | ${2}
+ ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${false} | ${3}
+ ${'state-5'} | ${'Removing'} | ${false} | ${true} | ${4}
`(
'displays the name and locked information "$name" for line "$lineNumber"',
- ({ name, toolTipText, locked, lineNumber }) => {
+ ({ name, toolTipText, locked, loading, lineNumber }) => {
const states = wrapper.findAll('[data-testid="terraform-states-table-name"]');
const state = states.at(lineNumber);
@@ -111,6 +139,7 @@ describe('StatesTable', () => {
expect(state.text()).toContain(name);
expect(state.find(GlIcon).exists()).toBe(locked);
+ expect(state.find(GlLoadingIcon).exists()).toBe(loading);
expect(toolTip.exists()).toBe(locked);
if (locked) {
@@ -154,6 +183,17 @@ describe('StatesTable', () => {
expect(findActions().length).toEqual(0);
});
+ it.each`
+ errorMessage | lineNumber
+ ${defaultProps.states[0].errorMessages[0]} | ${0}
+ ${defaultProps.states[3].errorMessages[0]} | ${1}
+ `('displays table error message "$errorMessage"', ({ errorMessage, lineNumber }) => {
+ const states = wrapper.findAll('[data-testid="terraform-states-table-error"]');
+ const state = states.at(lineNumber);
+
+ expect(state.text()).toBe(errorMessage);
+ });
+
describe('when user is a terraform administrator', () => {
beforeEach(() => {
return createComponent({
diff --git a/spec/frontend/terraform/components/terraform_list_spec.js b/spec/frontend/terraform/components/terraform_list_spec.js
index fb56a7135a3..882b7b55b3e 100644
--- a/spec/frontend/terraform/components/terraform_list_spec.js
+++ b/spec/frontend/terraform/components/terraform_list_spec.js
@@ -1,7 +1,8 @@
import { GlAlert, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTab } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
-import createMockApollo from 'helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import EmptyState from '~/terraform/components/empty_state.vue';
import StatesTable from '~/terraform/components/states_table.vue';
import TerraformList from '~/terraform/components/terraform_list.vue';
@@ -27,8 +28,20 @@ describe('TerraformList', () => {
},
};
+ const mockResolvers = {
+ TerraformState: {
+ _showDetails: jest.fn().mockResolvedValue(false),
+ errorMessages: jest.fn().mockResolvedValue([]),
+ loadingLock: jest.fn().mockResolvedValue(false),
+ loadingRemove: jest.fn().mockResolvedValue(false),
+ },
+ Mutation: {
+ addDataToTerraformState: jest.fn().mockResolvedValue({}),
+ },
+ };
+
const statsQueryResponse = queryResponse || jest.fn().mockResolvedValue(apolloQueryResponse);
- const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]]);
+ const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]], mockResolvers);
wrapper = shallowMount(TerraformList, {
localVue,
@@ -52,20 +65,28 @@ describe('TerraformList', () => {
describe('when there is a list of terraform states', () => {
const states = [
{
+ _showDetails: false,
+ errorMessages: [],
id: 'gid://gitlab/Terraform::State/1',
name: 'state-1',
+ latestVersion: null,
+ loadingLock: false,
+ loadingRemove: false,
lockedAt: null,
- updatedAt: null,
lockedByUser: null,
- latestVersion: null,
+ updatedAt: null,
},
{
+ _showDetails: false,
+ errorMessages: [],
id: 'gid://gitlab/Terraform::State/2',
name: 'state-2',
+ latestVersion: null,
+ loadingLock: false,
+ loadingRemove: false,
lockedAt: null,
- updatedAt: null,
lockedByUser: null,
- latestVersion: null,
+ updatedAt: null,
},
];
@@ -83,7 +104,7 @@ describe('TerraformList', () => {
},
});
- return wrapper.vm.$nextTick();
+ return waitForPromises();
});
it('displays a states tab and count', () => {
@@ -111,7 +132,7 @@ describe('TerraformList', () => {
},
});
- return wrapper.vm.$nextTick();
+ return waitForPromises();
});
it('renders the states table without pagination buttons', () => {
@@ -131,7 +152,7 @@ describe('TerraformList', () => {
},
});
- return wrapper.vm.$nextTick();
+ return waitForPromises();
});
it('displays a states tab with no count', () => {
@@ -149,7 +170,7 @@ describe('TerraformList', () => {
beforeEach(() => {
createWrapper({ terraformStates: null, queryResponse: jest.fn().mockRejectedValue() });
- return wrapper.vm.$nextTick();
+ return waitForPromises();
});
it('displays an alert message', () => {
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index a122b06fdda..b6b29faef79 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -1,13 +1,13 @@
+import { config as testUtilsConfig } from '@vue/test-utils';
+import * as jqueryMatchers from 'custom-jquery-matchers';
import Vue from 'vue';
import 'jquery';
-
-import * as jqueryMatchers from 'custom-jquery-matchers';
-import { config as testUtilsConfig } from '@vue/test-utils';
+import { setGlobalDateToFakeDate } from 'helpers/fake_date';
import Translate from '~/vue_shared/translate';
-import { initializeTestTimeout } from './__helpers__/timeout';
import { getJSONFixture, loadHTMLFixture, setHTMLFixture } from './__helpers__/fixtures';
-import { setupManualMocks } from './mocks/mocks_helper';
+import { initializeTestTimeout } from './__helpers__/timeout';
import customMatchers from './matchers';
+import { setupManualMocks } from './mocks/mocks_helper';
import './__helpers__/dom_shims';
import './__helpers__/jquery';
@@ -20,6 +20,10 @@ process.on('unhandledRejection', global.promiseRejectionHandler);
setupManualMocks();
+// Fake the `Date` for the rest of the jest spec runtime environment.
+// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39496#note_503084332
+setGlobalDateToFakeDate();
+
afterEach(() =>
// give Promises a bit more time so they fail the right test
new Promise(setImmediate).then(() => {
diff --git a/spec/frontend/tooltips/components/tooltips_spec.js b/spec/frontend/tooltips/components/tooltips_spec.js
index 3a894427643..e21626456e2 100644
--- a/spec/frontend/tooltips/components/tooltips_spec.js
+++ b/spec/frontend/tooltips/components/tooltips_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlTooltip } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Tooltips from '~/tooltips/components/tooltips.vue';
diff --git a/spec/frontend/tooltips/index_spec.js b/spec/frontend/tooltips/index_spec.js
index bff9ee0c7f2..9c03ca8f4c9 100644
--- a/spec/frontend/tooltips/index_spec.js
+++ b/spec/frontend/tooltips/index_spec.js
@@ -1,4 +1,3 @@
-import jQuery from 'jquery';
import {
add,
initTooltips,
@@ -146,29 +145,4 @@ describe('tooltips/index.js', () => {
expect(tooltipsApp.fixTitle).toHaveBeenCalledWith(target);
});
-
- describe('when glTooltipsEnabled feature flag is disabled', () => {
- beforeEach(() => {
- window.gon.features.glTooltips = false;
- });
-
- it.each`
- method | methodName | bootstrapParams
- ${dispose} | ${'dispose'} | ${'dispose'}
- ${fixTitle} | ${'fixTitle'} | ${'_fixTitle'}
- ${enable} | ${'enable'} | ${'enable'}
- ${disable} | ${'disable'} | ${'disable'}
- ${hide} | ${'hide'} | ${'hide'}
- ${show} | ${'show'} | ${'show'}
- ${add} | ${'init'} | ${{ title: 'the title' }}
- `('delegates $methodName to bootstrap tooltip API', ({ method, bootstrapParams }) => {
- const elements = jQuery(createTooltipTarget());
-
- jest.spyOn(jQuery.fn, 'tooltip');
-
- method(elements, bootstrapParams);
-
- expect(elements.tooltip).toHaveBeenCalledWith(bootstrapParams);
- });
- });
});
diff --git a/spec/frontend/user_lists/components/edit_user_list_spec.js b/spec/frontend/user_lists/components/edit_user_list_spec.js
index 958e86ac050..bd71a677a24 100644
--- a/spec/frontend/user_lists/components/edit_user_list_spec.js
+++ b/spec/frontend/user_lists/components/edit_user_list_spec.js
@@ -1,14 +1,14 @@
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import { createLocalVue, mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
-import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
-import createStore from '~/user_lists/store/edit';
+import { redirectTo } from '~/lib/utils/url_utility';
import EditUserList from '~/user_lists/components/edit_user_list.vue';
import UserListForm from '~/user_lists/components/user_list_form.vue';
+import createStore from '~/user_lists/store/edit';
import { userList } from '../../feature_flags/mock_data';
-import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/api');
jest.mock('~/lib/utils/url_utility');
diff --git a/spec/frontend/user_lists/components/new_user_list_spec.js b/spec/frontend/user_lists/components/new_user_list_spec.js
index 421b49f40e9..a81e8912714 100644
--- a/spec/frontend/user_lists/components/new_user_list_spec.js
+++ b/spec/frontend/user_lists/components/new_user_list_spec.js
@@ -1,12 +1,12 @@
+import { GlAlert } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { GlAlert } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
-import createStore from '~/user_lists/store/new';
-import NewUserList from '~/user_lists/components/new_user_list.vue';
import { redirectTo } from '~/lib/utils/url_utility';
+import NewUserList from '~/user_lists/components/new_user_list.vue';
+import createStore from '~/user_lists/store/new';
import { userList } from '../../feature_flags/mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/user_lists/components/user_list_spec.js b/spec/frontend/user_lists/components/user_list_spec.js
index cd00c421154..f016b5091d9 100644
--- a/spec/frontend/user_lists/components/user_list_spec.js
+++ b/spec/frontend/user_lists/components/user_list_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
+import { GlAlert, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { uniq } from 'lodash';
-import { GlAlert, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+import Vue from 'vue';
+import Vuex from 'vuex';
import Api from '~/api';
-import { parseUserIds, stringifyUserIds } from '~/user_lists/store/utils';
-import createStore from '~/user_lists/store/show';
import UserList from '~/user_lists/components/user_list.vue';
+import createStore from '~/user_lists/store/show';
+import { parseUserIds, stringifyUserIds } from '~/user_lists/store/utils';
import { userList } from '../../feature_flags/mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/user_lists/store/edit/actions_spec.js b/spec/frontend/user_lists/store/edit/actions_spec.js
index 7f0fb8e5401..c4b0f888d3e 100644
--- a/spec/frontend/user_lists/store/edit/actions_spec.js
+++ b/spec/frontend/user_lists/store/edit/actions_spec.js
@@ -1,9 +1,9 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import createState from '~/user_lists/store/edit/state';
-import * as types from '~/user_lists/store/edit/mutation_types';
-import * as actions from '~/user_lists/store/edit/actions';
import { redirectTo } from '~/lib/utils/url_utility';
+import * as actions from '~/user_lists/store/edit/actions';
+import * as types from '~/user_lists/store/edit/mutation_types';
+import createState from '~/user_lists/store/edit/state';
import { userList } from '../../../feature_flags/mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/user_lists/store/edit/mutations_spec.js b/spec/frontend/user_lists/store/edit/mutations_spec.js
index 3d4d2a59717..0943c64e934 100644
--- a/spec/frontend/user_lists/store/edit/mutations_spec.js
+++ b/spec/frontend/user_lists/store/edit/mutations_spec.js
@@ -1,7 +1,7 @@
import statuses from '~/user_lists/constants/edit';
-import createState from '~/user_lists/store/edit/state';
import * as types from '~/user_lists/store/edit/mutation_types';
import mutations from '~/user_lists/store/edit/mutations';
+import createState from '~/user_lists/store/edit/state';
import { userList } from '../../../feature_flags/mock_data';
describe('User List Edit Mutations', () => {
diff --git a/spec/frontend/user_lists/store/new/actions_spec.js b/spec/frontend/user_lists/store/new/actions_spec.js
index 9cc6212a125..916ec2e6da7 100644
--- a/spec/frontend/user_lists/store/new/actions_spec.js
+++ b/spec/frontend/user_lists/store/new/actions_spec.js
@@ -1,9 +1,9 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import createState from '~/user_lists/store/new/state';
-import * as types from '~/user_lists/store/new/mutation_types';
-import * as actions from '~/user_lists/store/new/actions';
import { redirectTo } from '~/lib/utils/url_utility';
+import * as actions from '~/user_lists/store/new/actions';
+import * as types from '~/user_lists/store/new/mutation_types';
+import createState from '~/user_lists/store/new/state';
import { userList } from '../../../feature_flags/mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/user_lists/store/new/mutations_spec.js b/spec/frontend/user_lists/store/new/mutations_spec.js
index 89e8a83eb25..a928849e941 100644
--- a/spec/frontend/user_lists/store/new/mutations_spec.js
+++ b/spec/frontend/user_lists/store/new/mutations_spec.js
@@ -1,6 +1,6 @@
-import createState from '~/user_lists/store/new/state';
import * as types from '~/user_lists/store/new/mutation_types';
import mutations from '~/user_lists/store/new/mutations';
+import createState from '~/user_lists/store/new/state';
describe('User List Edit Mutations', () => {
let state;
diff --git a/spec/frontend/user_lists/store/show/actions_spec.js b/spec/frontend/user_lists/store/show/actions_spec.js
index 25a6b9ec0e4..7b82fd8cf24 100644
--- a/spec/frontend/user_lists/store/show/actions_spec.js
+++ b/spec/frontend/user_lists/store/show/actions_spec.js
@@ -1,10 +1,10 @@
import testAction from 'helpers/vuex_action_helper';
import { userList } from 'jest/feature_flags/mock_data';
import Api from '~/api';
-import { stringifyUserIds } from '~/user_lists/store/utils';
-import createState from '~/user_lists/store/show/state';
-import * as types from '~/user_lists/store/show/mutation_types';
import * as actions from '~/user_lists/store/show/actions';
+import * as types from '~/user_lists/store/show/mutation_types';
+import createState from '~/user_lists/store/show/state';
+import { stringifyUserIds } from '~/user_lists/store/utils';
jest.mock('~/api');
diff --git a/spec/frontend/user_lists/store/show/mutations_spec.js b/spec/frontend/user_lists/store/show/mutations_spec.js
index cd379641ee1..92e2fcb06be 100644
--- a/spec/frontend/user_lists/store/show/mutations_spec.js
+++ b/spec/frontend/user_lists/store/show/mutations_spec.js
@@ -1,9 +1,9 @@
import { uniq } from 'lodash';
import { userList } from 'jest/feature_flags/mock_data';
-import createState from '~/user_lists/store/show/state';
-import mutations from '~/user_lists/store/show/mutations';
import { states } from '~/user_lists/constants/show';
import * as types from '~/user_lists/store/show/mutation_types';
+import mutations from '~/user_lists/store/show/mutations';
+import createState from '~/user_lists/store/show/state';
describe('User Lists Show Mutations', () => {
let mockState;
diff --git a/spec/frontend/user_popovers_spec.js b/spec/frontend/user_popovers_spec.js
index 1e0c4dd29ee..7c9c3d69efa 100644
--- a/spec/frontend/user_popovers_spec.js
+++ b/spec/frontend/user_popovers_spec.js
@@ -1,5 +1,5 @@
-import initUserPopovers from '~/user_popovers';
import UsersCache from '~/lib/utils/users_cache';
+import initUserPopovers from '~/user_popovers';
describe('User Popovers', () => {
const fixtureTemplate = 'merge_requests/merge_request_with_mentions.html';
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
index fd8b0dddc61..d6a1c2d3b07 100644
--- a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
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 { deprecatedCreateFlash as createFlash } from '~/flash';
import {
FETCH_LOADING,
FETCH_ERROR,
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
index d9a5230f55f..65cafc647e0 100644
--- a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
const TEST_HELP_PATH = 'help/path';
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
index b8ba619fbb1..ef712ec23a6 100644
--- a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages';
-import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
import { toNounSeriesText } from '~/lib/utils/grammar';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages';
import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
const testApprovers = () => Array.from({ length: 5 }, (_, i) => i).map((id) => ({ id }));
diff --git a/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js b/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js
index 2e1e21299b3..b2cc7d9be6b 100644
--- a/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js
+++ b/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js
@@ -1,8 +1,8 @@
+import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vuex from 'vuex';
-import Vue, { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import { GlLoadingIcon } from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
import { TEST_HOST as FAKE_ENDPOINT } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
import ArtifactsListApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue';
diff --git a/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js b/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js
index fd432381512..712abfe228a 100644
--- a/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js
+++ b/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ArtifactsList from '~/vue_merge_request_widget/components/artifacts_list.vue';
import { artifacts } from '../mock_data';
diff --git a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js
index ba2a8ee0a41..94d4cccab5f 100644
--- a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_collapsible_extension.vue';
describe('Merge Request Collapsible Extension', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js
index 720ce613b85..07e869a070f 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import MrWidgetAlertMessage from '~/vue_merge_request_widget/components/mr_widget_alert_message.vue';
describe('MrWidgetAlertMessage', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
index 4bcae904ddc..ebd10f31fa7 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
const TEST_ICON = 'commit';
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 79a0dd1e760..f55d313a719 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -1,6 +1,6 @@
-import Vue from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
index 7ff8d9678fe..3baade5161e 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
@@ -1,10 +1,10 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue';
-import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import axios from '~/lib/utils/axios_utils';
import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue';
+import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue';
import { mockStore } from '../mock_data';
-import axios from '~/lib/utils/axios_utils';
describe('MrWidgetPipelineContainer', () => {
let wrapper;
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 3e5ab5cd32d..b93236d4628 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import { SUCCESS } from '~/vue_merge_request_widget/constants';
-import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
+import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import { SUCCESS } from '~/vue_merge_request_widget/constants';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js
index bdd038edd71..5081e1e5906 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js
@@ -1,7 +1,7 @@
-import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
-import eventHub from '~/vue_merge_request_widget/event_hub';
+import { nextTick } from 'vue';
import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
let wrapper;
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js
index 6c3b4a01659..c25e10c5249 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -1,48 +1,60 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('MR widget status icon component', () => {
- let vm;
- let Component;
+ let wrapper;
- beforeEach(() => {
- Component = Vue.extend(mrStatusIcon);
- });
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findDisabledMergeButton = () => wrapper.find('[data-testid="disabled-merge-button"]');
+
+ const createWrapper = (props, mountFn = shallowMount) => {
+ wrapper = mountFn(mrStatusIcon, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('while loading', () => {
it('renders loading icon', () => {
- vm = mountComponent(Component, { status: 'loading' });
+ createWrapper({ status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
+ expect(findLoadingIcon().exists()).toBe(true);
});
});
describe('with status icon', () => {
- it('renders ci status icon', () => {
- vm = mountComponent(Component, { status: 'failed' });
+ it('renders success status icon', () => {
+ createWrapper({ status: 'success' }, mount);
+
+ expect(wrapper.find('[data-testid="status_success-icon"]').exists()).toBe(true);
+ });
+
+ it('renders failed status icon', () => {
+ createWrapper({ status: 'failed' }, mount);
- expect(vm.$el.querySelector('.js-ci-status-icon-failed')).not.toBeNull();
+ expect(wrapper.find('[data-testid="status_failed-icon"]').exists()).toBe(true);
});
});
describe('with disabled button', () => {
it('renders a disabled button', () => {
- vm = mountComponent(Component, { status: 'failed', showDisabledButton: true });
+ createWrapper({ status: 'failed', showDisabledButton: true });
- expect(vm.$el.querySelector('.js-disabled-merge-button').textContent.trim()).toEqual('Merge');
+ expect(findDisabledMergeButton().exists()).toBe(true);
});
});
describe('without disabled button', () => {
it('does not render a disabled button', () => {
- vm = mountComponent(Component, { status: 'failed' });
+ createWrapper({ status: 'failed' });
- expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeNull();
+ expect(findDisabledMergeButton().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
index 8fcc982ac99..b5afc1ab21a 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -1,12 +1,10 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
+import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
-import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
-import dismissibleContainer from '~/vue_shared/components/dismissible_container.vue';
-import { suggestProps, iconName } from './pipeline_tour_mock_data';
+import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import axios from '~/lib/utils/axios_utils';
+import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
+import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
import {
SP_TRACK_LABEL,
SP_LINK_TRACK_EVENT,
@@ -15,6 +13,8 @@ import {
SP_SHOW_TRACK_VALUE,
SP_HELP_URL,
} from '~/vue_merge_request_widget/constants';
+import dismissibleContainer from '~/vue_shared/components/dismissible_container.vue';
+import { suggestProps, iconName } from './pipeline_tour_mock_data';
describe('MRWidgetSuggestPipeline', () => {
describe('template', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
new file mode 100644
index 00000000000..a124008b36a
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PipelineFailed should render error message with a disabled merge button 1`] = `
+<div
+ class="mr-widget-body media"
+>
+ <status-icon-stub
+ showdisabledbutton="true"
+ status="warning"
+ />
+
+ <div
+ class="media-body space-children"
+ >
+ <span
+ class="bold"
+ >
+ <gl-sprintf-stub
+ message="The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the %{linkStart}troubleshooting documentation%{linkEnd} to see other possible actions."
+ />
+ </span>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index 850bbd93df5..4dd1bd2aa9c 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -1,11 +1,11 @@
-import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import eventHub from '~/vue_merge_request_widget/event_hub';
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
let wrapper;
let mergeRequestWidgetGraphqlEnabled = false;
@@ -202,7 +202,11 @@ describe('MRWidgetAutoMergeEnabled', () => {
wrapper.vm.cancelAutomaticMerge();
setImmediate(() => {
expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
- expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
+ if (mergeRequestWidgetGraphql) {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ } else {
+ expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
+ }
done();
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index dca3798f7ea..24198096564 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -1,6 +1,6 @@
-import { nextTick } from 'vue';
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import AutoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index 706d60368b5..4c763f40cbe 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
const commits = [
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index f5a059698b6..d3fc1e0e05b 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,7 +1,7 @@
-import $ from 'jquery';
import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { removeBreakLine } from 'helpers/text_helper';
+import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
+import { removeBreakLine } from 'helpers/text_helper';
import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
describe('MRWidgetConflicts', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
index 48c1a9eedf9..c1471314c4a 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -54,7 +54,7 @@ describe('MRWidgetFailedToMerge', () => {
Vue.nextTick()
.then(() => {
- expect(vm.mergeError).toBe('contains line breaks');
+ expect(vm.mergeError).toBe('contains line breaks.');
})
.then(done)
.catch(done.fail);
@@ -113,14 +113,14 @@ describe('MRWidgetFailedToMerge', () => {
describe('while it is not regresing', () => {
it('renders warning icon and disabled merge button', () => {
expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
- expect(vm.$el.querySelector('.js-disabled-merge-button').getAttribute('disabled')).toEqual(
- 'disabled',
- );
+ expect(
+ vm.$el.querySelector('[data-testid="disabled-merge-button"]').getAttribute('disabled'),
+ ).toEqual('disabled');
});
it('renders given error', () => {
expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual(
- 'Merge error happened',
+ 'Merge error happened.',
);
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js
index 36c4174c03d..6af8ac9e18e 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -1,5 +1,8 @@
+import { getByRole } from '@testing-library/dom';
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
+import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
+import modalEventHub from '~/projects/commit/event_hub';
import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
@@ -16,6 +19,7 @@ describe('MRWidgetMerged', () => {
};
beforeEach(() => {
+ jest.spyOn(document, 'dispatchEvent');
const Component = Vue.extend(mergedComponent);
const mr = {
isRemovingSourceBranch: false,
@@ -147,6 +151,26 @@ describe('MRWidgetMerged', () => {
});
});
+ it('calls dispatchDocumentEvent to load in the modal component', () => {
+ expect(document.dispatchEvent).toHaveBeenCalledWith(new CustomEvent('merged:UpdateActions'));
+ });
+
+ it('emits event to open the revert modal on revert button click', () => {
+ const eventHubSpy = jest.spyOn(modalEventHub, '$emit');
+
+ getByRole(vm.$el, 'button', { name: /Revert/i }).click();
+
+ expect(eventHubSpy).toHaveBeenCalledWith(OPEN_REVERT_MODAL);
+ });
+
+ it('emits event to open the cherry-pick modal on cherry-pick button click', () => {
+ const eventHubSpy = jest.spyOn(modalEventHub, '$emit');
+
+ getByRole(vm.$el, 'button', { name: /Cherry-pick/i }).click();
+
+ expect(eventHubSpy).toHaveBeenCalledWith(OPEN_CHERRY_PICK_MODAL);
+ });
+
it('has merged by information', () => {
expect(vm.$el.textContent).toContain('Merged by');
expect(vm.$el.textContent).toContain('Administrator');
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
index 8847e4e6bdd..bd77a1d657e 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
@@ -1,25 +1,27 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import { removeBreakLine } from 'helpers/text_helper';
-import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
+import { shallowMount, mount } from '@vue/test-utils';
+import PipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
describe('MRWidgetPipelineBlocked', () => {
- let vm;
- beforeEach(() => {
- const Component = Vue.extend(pipelineBlockedComponent);
- vm = mountComponent(Component);
- });
+ let wrapper;
+
+ const createWrapper = (mountFn = shallowMount) => {
+ wrapper = mountFn(PipelineBlockedComponent);
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('renders warning icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon-warning')).not.toBe(null);
+ createWrapper(mount);
+
+ expect(wrapper.find('.ci-status-icon-warning').exists()).toBe(true);
});
it('renders information text', () => {
- expect(removeBreakLine(vm.$el.textContent).trim()).toContain(
+ createWrapper();
+
+ expect(wrapper.text()).toBe(
'Pipeline blocked. The pipeline for this merge request requires a manual action to proceed',
);
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
index 179adef12d9..3e0840fef4e 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
@@ -1,19 +1,30 @@
-import Vue from 'vue';
-import { removeBreakLine } from 'helpers/text_helper';
+import { shallowMount } from '@vue/test-utils';
+import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
describe('PipelineFailed', () => {
- describe('template', () => {
- const Component = Vue.extend(PipelineFailed);
- const vm = new Component({
- el: document.createElement('div'),
- });
- it('should have correct elements', () => {
- expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(removeBreakLine(vm.$el.innerText).trim()).toContain(
- 'The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure',
- );
- });
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(PipelineFailed);
+ };
+
+ const findStatusIcon = () => wrapper.find(statusIcon);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render error message with a disabled merge button', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('merge button should be disabled', () => {
+ expect(findStatusIcon().props('showDisabledButton')).toBe(true);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 8eddf59820c..983e4a35078 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -1,14 +1,14 @@
-import Vue from 'vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
-import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
-import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
-import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import Vue from 'vue';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
+import simplePoll from '~/lib/utils/simple_poll';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
-import eventHub from '~/vue_merge_request_widget/event_hub';
+import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
+import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import { MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import simplePoll from '~/lib/utils/simple_poll';
+import eventHub from '~/vue_merge_request_widget/event_hub';
jest.mock('~/lib/utils/simple_poll', () =>
jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
index 38920846a50..ef6a9b1e8fc 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
import { removeBreakLine } from 'helpers/text_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
describe('ShaMismatch', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index cc160f6182d..8ead0002950 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -1,5 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlFormCheckbox } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n';
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index a5531577a8c..6c0d69ea109 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
-import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
import notesEventHub from '~/notes/event_hub';
+import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
function createComponent({ path = '' } = {}) {
return mount(UnresolvedDiscussions, {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
index 6d63d4b1be3..e0077a008a2 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
index 8da0d0f16d6..364f849eb4f 100644
--- a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
+++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -1,12 +1,12 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { invalidPlanWithName, plans, validPlanWithName } from './mock_data';
import axios from '~/lib/utils/axios_utils';
+import Poll from '~/lib/utils/poll';
import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
import MrWidgetTerraformContainer from '~/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue';
-import Poll from '~/lib/utils/poll';
import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+import { invalidPlanWithName, plans, validPlanWithName } from './mock_data';
describe('MrWidgetTerraformConainer', () => {
let mock;
diff --git a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
index ea4eb44ebfe..f95a92c2cb1 100644
--- a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
+++ b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
@@ -33,7 +33,7 @@ describe('TerraformPlan', () => {
it('diplays the header text with a name', () => {
expect(wrapper.text()).toContain(
- `The Terraform report ${validPlanWithName.job_name} was generated in your pipelines.`,
+ `The report ${validPlanWithName.job_name} was generated in your pipelines.`,
);
});
@@ -55,7 +55,7 @@ describe('TerraformPlan', () => {
});
it('diplays the header text without a name', () => {
- expect(wrapper.text()).toContain('A Terraform report was generated in your pipelines.');
+ expect(wrapper.text()).toContain('A report was generated in your pipelines.');
});
});
@@ -70,7 +70,7 @@ describe('TerraformPlan', () => {
it('diplays the header text with a name', () => {
expect(wrapper.text()).toContain(
- `The Terraform report ${invalidPlanWithName.job_name} failed to generate.`,
+ `The report ${invalidPlanWithName.job_name} failed to generate.`,
);
});
@@ -85,7 +85,7 @@ describe('TerraformPlan', () => {
});
it('diplays the header text without a name', () => {
- expect(wrapper.text()).toContain('A Terraform report failed to generate.');
+ expect(wrapper.text()).toContain('A report failed to generate.');
});
it('does not render button because url is missing', () => {
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js
index bc0d2501809..8c5036e35f6 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js
@@ -1,12 +1,12 @@
-import { mount } from '@vue/test-utils';
import { GlIcon, GlLoadingIcon, GlButton } from '@gitlab/ui';
-import DeploymentActionButton from '~/vue_merge_request_widget/components/deployment/deployment_action_button.vue';
+import { mount } from '@vue/test-utils';
import {
CREATED,
RUNNING,
DEPLOYING,
REDEPLOYING,
} from '~/vue_merge_request_widget/components/deployment/constants';
+import DeploymentActionButton from '~/vue_merge_request_widget/components/deployment/deployment_action_button.vue';
import { actionButtonMocks } from './deployment_mock_data';
const baseProps = {
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
index 13c0665f929..22e58ac6abf 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
@@ -1,8 +1,6 @@
import { mount } from '@vue/test-utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { visitUrl } from '~/lib/utils/url_utility';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import DeploymentActions from '~/vue_merge_request_widget/components/deployment/deployment_actions.vue';
import {
CREATED,
MANUAL_DEPLOY,
@@ -11,6 +9,8 @@ import {
REDEPLOYING,
STOPPING,
} from '~/vue_merge_request_widget/components/deployment/constants';
+import DeploymentActions from '~/vue_merge_request_widget/components/deployment/deployment_actions.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import {
actionButtonMocks,
deploymentMockData,
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
index 87bf32351bd..c27cbd8b781 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
@@ -1,7 +1,4 @@
import { mount } from '@vue/test-utils';
-import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
-import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
-import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import {
CREATED,
RUNNING,
@@ -10,6 +7,9 @@ import {
CANCELED,
SKIPPED,
} from '~/vue_merge_request_widget/components/deployment/constants';
+import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
+import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
+import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import { deploymentMockData, playDetails, retryDetails } from './deployment_mock_data';
describe('Deployment component', () => {
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index 1ea7fe1fbfe..7b020813bd5 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -1,28 +1,26 @@
import { mount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import Api from '~/api';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { securityReportDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
import axios from '~/lib/utils/axios_utils';
-import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
-import eventHub from '~/vue_merge_request_widget/event_hub';
+import { setFaviconOverlay } from '~/lib/utils/favicon';
import notify from '~/lib/utils/notify';
import SmartInterval from '~/smart_interval';
-import { setFaviconOverlay } from '~/lib/utils/favicon';
+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';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
-import mockData from './mock_data';
+import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
-import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+import mockData from './mock_data';
jest.mock('~/smart_interval');
jest.mock('~/lib/utils/favicon');
-const returnPromise = (data) =>
- new Promise((resolve) => {
- resolve({
- data,
- });
- });
+Vue.use(VueApollo);
describe('MrWidgetOptions', () => {
let wrapper;
@@ -48,7 +46,7 @@ describe('MrWidgetOptions', () => {
gon.features = {};
});
- const createComponent = (mrData = mockData) => {
+ const createComponent = (mrData = mockData, options = {}) => {
if (wrapper) {
wrapper.destroy();
}
@@ -57,6 +55,7 @@ describe('MrWidgetOptions', () => {
propsData: {
mrData: { ...mrData },
},
+ ...options,
});
return axios.waitForAll();
@@ -68,6 +67,7 @@ describe('MrWidgetOptions', () => {
describe('default', () => {
beforeEach(() => {
+ jest.spyOn(document, 'dispatchEvent');
return createComponent();
});
@@ -281,7 +281,7 @@ describe('MrWidgetOptions', () => {
let isCbExecuted;
beforeEach(() => {
- jest.spyOn(wrapper.vm.service, 'checkStatus').mockReturnValue(returnPromise(mockData));
+ jest.spyOn(wrapper.vm.service, 'checkStatus').mockResolvedValue({ data: mockData });
jest.spyOn(wrapper.vm.mr, 'setData').mockImplementation(() => {});
jest.spyOn(wrapper.vm, 'handleNotification').mockImplementation(() => {});
@@ -331,7 +331,7 @@ describe('MrWidgetOptions', () => {
it('should fetch deployments', () => {
jest
.spyOn(wrapper.vm.service, 'fetchDeployments')
- .mockReturnValue(returnPromise([{ id: 1, status: SUCCESS }]));
+ .mockResolvedValue({ data: [{ id: 1, status: SUCCESS }] });
wrapper.vm.fetchPreMergeDeployments();
@@ -347,13 +347,16 @@ describe('MrWidgetOptions', () => {
it('should fetch content of Cherry Pick and Revert modals', () => {
jest
.spyOn(wrapper.vm.service, 'fetchMergeActionsContent')
- .mockReturnValue(returnPromise('hello world'));
+ .mockResolvedValue({ data: 'hello world' });
wrapper.vm.fetchActionsContent();
return nextTick().then(() => {
expect(wrapper.vm.service.fetchMergeActionsContent).toHaveBeenCalled();
expect(document.body.textContent).toContain('hello world');
+ expect(document.dispatchEvent).toHaveBeenCalledWith(
+ new CustomEvent('merged:UpdateActions'),
+ );
});
});
});
@@ -822,36 +825,34 @@ describe('MrWidgetOptions', () => {
describe('security widget', () => {
describe.each`
- context | hasPipeline | reportType | isFlagEnabled | shouldRender
- ${'security report and flag enabled'} | ${true} | ${'sast'} | ${true} | ${true}
- ${'security report and flag disabled'} | ${true} | ${'sast'} | ${false} | ${false}
- ${'no security report and flag enabled'} | ${true} | ${'foo'} | ${true} | ${false}
- ${'no pipeline and flag enabled'} | ${false} | ${'sast'} | ${true} | ${false}
- `('given $context', ({ hasPipeline, reportType, isFlagEnabled, shouldRender }) => {
+ context | hasPipeline | shouldRender
+ ${'there is a pipeline'} | ${true} | ${true}
+ ${'no pipeline'} | ${false} | ${false}
+ `('given $context', ({ hasPipeline, shouldRender }) => {
beforeEach(() => {
- gon.features.coreSecurityMrWidget = isFlagEnabled;
+ const mrData = {
+ ...mockData,
+ ...(hasPipeline ? {} : { pipeline: null }),
+ };
- if (hasPipeline) {
- jest.spyOn(Api, 'pipelineJobs').mockResolvedValue({
- data: [{ artifacts: [{ file_type: reportType }] }],
- });
- }
+ // Override top-level mocked requests, which always use a fresh copy of
+ // mockData, which always includes the full pipeline object.
+ mock.onGet(mockData.merge_request_widget_path).reply(() => [200, mrData]);
+ mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [200, mrData]);
- return createComponent({
- ...mockData,
- ...(hasPipeline ? {} : { pipeline: undefined }),
+ return createComponent(mrData, {
+ apolloProvider: createMockApollo([
+ [
+ securityReportDownloadPathsQuery,
+ async () => ({ data: securityReportDownloadPathsQueryResponse }),
+ ],
+ ]),
});
});
- if (shouldRender) {
- it('renders', () => {
- expect(findSecurityMrWidget().exists()).toBe(true);
- });
- } else {
- it('does not render', () => {
- expect(findSecurityMrWidget().exists()).toBe(false);
- });
- }
+ it(shouldRender ? 'renders' : 'does not render', () => {
+ expect(findSecurityMrWidget().exists()).toBe(shouldRender);
+ });
});
});
diff --git a/spec/frontend/vue_mr_widget/stores/artifacts_list/actions_spec.js b/spec/frontend/vue_mr_widget/stores/artifacts_list/actions_spec.js
index 1dfa7564535..9423fa17c44 100644
--- a/spec/frontend/vue_mr_widget/stores/artifacts_list/actions_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/artifacts_list/actions_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import {
setEndpoint,
@@ -11,8 +11,8 @@ import {
receiveArtifactsSuccess,
receiveArtifactsError,
} from '~/vue_merge_request_widget/stores/artifacts_list/actions';
-import state from '~/vue_merge_request_widget/stores/artifacts_list/state';
import * as types from '~/vue_merge_request_widget/stores/artifacts_list/mutation_types';
+import state from '~/vue_merge_request_widget/stores/artifacts_list/state';
describe('Artifacts App Store Actions', () => {
let mockedState;
diff --git a/spec/frontend/vue_mr_widget/stores/artifacts_list/mutations_spec.js b/spec/frontend/vue_mr_widget/stores/artifacts_list/mutations_spec.js
index ea89fdb72e9..a4e6788c7f6 100644
--- a/spec/frontend/vue_mr_widget/stores/artifacts_list/mutations_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/artifacts_list/mutations_spec.js
@@ -1,6 +1,6 @@
-import state from '~/vue_merge_request_widget/stores/artifacts_list/state';
-import mutations from '~/vue_merge_request_widget/stores/artifacts_list/mutations';
import * as types from '~/vue_merge_request_widget/stores/artifacts_list/mutation_types';
+import mutations from '~/vue_merge_request_widget/stores/artifacts_list/mutations';
+import state from '~/vue_merge_request_widget/stores/artifacts_list/state';
describe('Artifacts Store Mutations', () => {
let stateCopy;
diff --git a/spec/frontend/alert_management/components/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
index 976e50625a6..dd9a7be6268 100644
--- a/spec/frontend/alert_management/components/alert_details_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
@@ -4,17 +4,14 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import AlertDetails from '~/alert_management/components/alert_details.vue';
-import AlertSummaryRow from '~/alert_management/components/alert_summary_row.vue';
-import {
- ALERTS_SEVERITY_LABELS,
- trackAlertsDetailsViewsOptions,
-} from '~/alert_management/constants';
-import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql';
import { joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
+import AlertDetails from '~/vue_shared/alert_details/components/alert_details.vue';
+import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue';
+import { SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants';
+import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
-import mockAlerts from '../mocks/alerts.json';
+import mockAlerts from './mocks/alerts.json';
const mockAlert = mockAlerts[0];
const environmentName = 'Production';
@@ -29,7 +26,13 @@ describe('AlertDetails', () => {
const projectId = '1';
const $router = { replace: jest.fn() };
- function mountComponent({ data, loading = false, mountMethod = shallowMount, stubs = {} } = {}) {
+ function mountComponent({
+ data,
+ loading = false,
+ mountMethod = shallowMount,
+ provide = {},
+ stubs = {},
+ } = {}) {
wrapper = extendedWrapper(
mountMethod(AlertDetails, {
provide: {
@@ -37,6 +40,7 @@ describe('AlertDetails', () => {
projectPath,
projectIssuesPath,
projectId,
+ ...provide,
},
data() {
return {
@@ -86,6 +90,7 @@ describe('AlertDetails', () => {
const findEnvironmentName = () => wrapper.findByTestId('environmentName');
const findEnvironmentPath = () => wrapper.findByTestId('environmentPath');
const findDetailsTable = () => wrapper.find(AlertDetailsTable);
+ const findMetricsTab = () => wrapper.findByTestId('metrics');
describe('Alert details', () => {
describe('when alert is null', () => {
@@ -112,9 +117,7 @@ describe('AlertDetails', () => {
});
it('renders severity', () => {
- expect(wrapper.findByTestId('severity').text()).toBe(
- ALERTS_SEVERITY_LABELS[mockAlert.severity],
- );
+ expect(wrapper.findByTestId('severity').text()).toBe(SEVERITY_LEVELS[mockAlert.severity]);
});
it('renders a title', () => {
@@ -173,6 +176,15 @@ describe('AlertDetails', () => {
});
});
+ describe('Threat Monitoring details', () => {
+ it('should not render the metrics tab', () => {
+ mountComponent({
+ data: { alert: mockAlert, provide: { isThreatMonitoringPage: true } },
+ });
+ expect(findMetricsTab().exists()).toBe(false);
+ });
+ });
+
describe('Create incident from alert', () => {
it('should display "View incident" button that links the incident page when incident exists', () => {
const issueIid = '3';
@@ -321,16 +333,27 @@ describe('AlertDetails', () => {
});
describe('Snowplow tracking', () => {
+ const mountOptions = {
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alert: mockAlert },
+ loading: false,
+ };
+
beforeEach(() => {
jest.spyOn(Tracking, 'event');
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alert: mockAlert },
- loading: false,
- });
});
- it('should track alert details page views', () => {
+ it('should not track alert details page views when the tracking options do not exist', () => {
+ mountComponent(mountOptions);
+ expect(Tracking.event).not.toHaveBeenCalled();
+ });
+
+ it('should track alert details page views when the tracking options exist', () => {
+ const trackAlertsDetailsViewsOptions = {
+ category: 'Alert Management',
+ action: 'view_alert_details',
+ };
+ mountComponent({ ...mountOptions, provide: { trackAlertsDetailsViewsOptions } });
const { category, action } = trackAlertsDetailsViewsOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
diff --git a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js
index ea7b4584a63..87ad5e36564 100644
--- a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
-import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue';
-import createAlertTodoMutation from '~/alert_management/graphql/mutations/alert_todo_create.mutation.graphql';
import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
-import mockAlerts from '../mocks/alerts.json';
+import SidebarTodo from '~/vue_shared/alert_details/components/sidebar/sidebar_todo.vue';
+import createAlertTodoMutation from '~/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql';
+import mockAlerts from './mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -59,7 +59,7 @@ describe('Alert Details Sidebar To Do', () => {
it('renders a button for adding a To-Do', async () => {
await wrapper.vm.$nextTick();
- expect(findToDoButton().text()).toBe('Add a To-Do');
+ expect(findToDoButton().text()).toBe('Add a to do');
});
it('calls `$apollo.mutate` with `createAlertTodoMutation` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', async () => {
diff --git a/spec/frontend/alert_management/components/alert_metrics_spec.js b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
index 42da8c3768b..b5a61a4adc1 100644
--- a/spec/frontend/alert_management/components/alert_metrics_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
-import waitForPromises from 'helpers/wait_for_promises';
-import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
-import AlertMetrics from '~/alert_management/components/alert_metrics.vue';
+import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
+import AlertMetrics from '~/vue_shared/alert_details/components/alert_metrics.vue';
jest.mock('~/monitoring/stores', () => ({
monitoringDashboard: {},
diff --git a/spec/frontend/alert_management/components/alert_status_spec.js b/spec/frontend/vue_shared/alert_details/alert_status_spec.js
index 6f2ddb86020..a866fc13539 100644
--- a/spec/frontend/alert_management/components/alert_status_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_status_spec.js
@@ -1,11 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
-import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
-import AlertManagementStatus from '~/alert_management/components/alert_status.vue';
-import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql';
+import updateAlertStatusMutation from '~/graphql_shared//mutations/alert_status_update.mutation.graphql';
import Tracking from '~/tracking';
-import mockAlerts from '../mocks/alerts.json';
+import AlertManagementStatus from '~/vue_shared/alert_details/components/alert_status.vue';
+import mockAlerts from './mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -20,7 +19,7 @@ describe('AlertManagementStatus', () => {
return waitForPromises();
};
- function mountComponent({ props = {}, loading = false, stubs = {} } = {}) {
+ function mountComponent({ props = {}, provide = {}, loading = false, stubs = {} } = {}) {
wrapper = shallowMount(AlertManagementStatus, {
propsData: {
alert: { ...mockAlert },
@@ -28,6 +27,7 @@ describe('AlertManagementStatus', () => {
isSidebar: false,
...props,
},
+ provide,
mocks: {
$apollo: {
mutate: jest.fn(),
@@ -134,10 +134,25 @@ describe('AlertManagementStatus', () => {
describe('Snowplow tracking', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
+ });
+
+ it('should not track alert status updates when the tracking options do not exist', () => {
mountComponent({});
+ Tracking.event.mockClear();
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
+ findFirstStatusOption().vm.$emit('click');
+ setImmediate(() => {
+ expect(Tracking.event).not.toHaveBeenCalled();
+ });
});
- it('should track alert status updates', () => {
+ it('should track alert status updates when the tracking options exist', () => {
+ const trackAlertStatusUpdateOptions = {
+ category: 'Alert Management',
+ action: 'update_alert_status',
+ label: 'Status',
+ };
+ mountComponent({ provide: { trackAlertStatusUpdateOptions } });
Tracking.event.mockClear();
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
findFirstStatusOption().vm.$emit('click');
diff --git a/spec/frontend/alert_management/components/alert_summary_row_spec.js b/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js
index 47c715c089a..a2981478954 100644
--- a/spec/frontend/alert_management/components/alert_summary_row_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import AlertSummaryRow from '~/alert_management/components/alert_summary_row.vue';
+import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue';
const label = 'a label';
const value = 'a value';
diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/vue_shared/alert_details/mocks/alerts.json
index 5267a4fe50d..5267a4fe50d 100644
--- a/spec/frontend/alert_management/mocks/alerts.json
+++ b/spec/frontend/vue_shared/alert_details/mocks/alerts.json
diff --git a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js
index 00c479071fe..28646994ed1 100644
--- a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
+++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js
@@ -1,11 +1,11 @@
+import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { GlDropdownItem } from '@gitlab/ui';
-import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue';
-import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql';
-import mockAlerts from '../../mocks/alerts.json';
+import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue';
+import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue';
+import AlertSetAssignees from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
+import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[0];
diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js
index 5235ae63fee..70cf2597963 100644
--- a/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
+++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js
@@ -1,9 +1,10 @@
import { shallowMount, mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import AlertSidebar from '~/alert_management/components/alert_sidebar.vue';
-import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import mockAlerts from '../../mocks/alerts.json';
+import AlertSidebar from '~/vue_shared/alert_details/components/alert_sidebar.vue';
+import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue';
+import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
+import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -11,7 +12,12 @@ describe('Alert Details Sidebar', () => {
let wrapper;
let mock;
- function mountComponent({ mountMethod = shallowMount, stubs = {}, alert = {} } = {}) {
+ function mountComponent({
+ mountMethod = shallowMount,
+ stubs = {},
+ alert = {},
+ provide = {},
+ } = {}) {
wrapper = mountMethod(AlertSidebar, {
data() {
return {
@@ -24,6 +30,7 @@ describe('Alert Details Sidebar', () => {
provide: {
projectPath: 'projectPath',
projectId: '1',
+ ...provide,
},
stubs,
mocks: {
@@ -60,5 +67,29 @@ describe('Alert Details Sidebar', () => {
});
expect(wrapper.find(SidebarAssignees).exists()).toBe(true);
});
+
+ it('should render side bar status dropdown', () => {
+ mountComponent({
+ mountMethod: mount,
+ alert: mockAlert,
+ });
+ expect(wrapper.find(SidebarStatus).exists()).toBe(true);
+ });
+ });
+
+ describe('the sidebar renders for threat monitoring', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mountComponent();
+ });
+
+ it('should not render side bar status dropdown', () => {
+ mountComponent({
+ mountMethod: mount,
+ alert: mockAlert,
+ provide: { isThreatMonitoringPage: true },
+ });
+ expect(wrapper.find(SidebarStatus).exists()).toBe(false);
+ });
});
});
diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js
index 0b60a36cf54..f5b9efb4d98 100644
--- a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
+++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js
@@ -1,10 +1,8 @@
-import { mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
-import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
-import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql';
-import Tracking from '~/tracking';
-import mockAlerts from '../../mocks/alerts.json';
+import { mount } from '@vue/test-utils';
+import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql';
+import AlertSidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
+import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -101,30 +99,5 @@ describe('Alert Details Sidebar Status', () => {
expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
});
});
-
- describe('Snowplow tracking', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alert: mockAlert },
- loading: false,
- });
- });
-
- it('should track alert status updates', () => {
- Tracking.event.mockClear();
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
- findStatusDropdownItem().vm.$emit('click');
- const status = findStatusDropdownItem().text();
- setImmediate(() => {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action, {
- label,
- property: status,
- });
- });
- });
- });
});
});
diff --git a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js
index 65cfc600d76..a5a9fb55737 100644
--- a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
+++ b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
-import SystemNote from '~/alert_management/components/system_notes/system_note.vue';
-import mockAlerts from '../../mocks/alerts.json';
+import { shallowMount } from '@vue/test-utils';
+import SystemNote from '~/vue_shared/alert_details/components/system_notes/system_note.vue';
+import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[1];
diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
index 20ea897e29c..3be609f0dad 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
@@ -18,13 +18,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="thumbsup"
/>
-
-
</span>
<span
@@ -52,13 +48,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="thumbsdown"
/>
-
-
</span>
<span
@@ -86,13 +78,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="smile"
/>
-
-
</span>
<span
@@ -120,13 +108,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="ok_hand"
/>
-
-
</span>
<span
@@ -154,13 +138,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="cactus"
/>
-
-
</span>
<span
@@ -188,13 +168,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="a"
/>
-
-
</span>
<span
@@ -222,13 +198,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="award-emoji-block"
data-testid="award-html"
>
-
-
<gl-emoji
data-name="b"
/>
-
-
</span>
<span
diff --git a/spec/frontend/vue_shared/components/actions_button_spec.js b/spec/frontend/vue_shared/components/actions_button_spec.js
index 2ac4bfda29a..e5b7b693cb5 100644
--- a/spec/frontend/vue_shared/components/actions_button_spec.js
+++ b/spec/frontend/vue_shared/components/actions_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownDivider, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
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 22643a17b2b..f592db935ec 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
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
+import { handleBlobRichViewer } from '~/blob/viewer';
import RichViewer from '~/vue_shared/components/blob_viewers/rich_viewer.vue';
import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue';
-import { handleBlobRichViewer } from '~/blob/viewer';
jest.mock('~/blob/viewer');
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 8434fdaccde..9a0616343fe 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants';
+import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
describe('Blob Simple Viewer component', () => {
let wrapper;
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 80918c5e771..6b9658a6d18 100644
--- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
const changedFile = () => ({ changed: true });
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 a633ef65aa4..a943d931f67 100644
--- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
@@ -1,10 +1,9 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import ciBadge from '~/vue_shared/components/ci_badge_link.vue';
+import { shallowMount } from '@vue/test-utils';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
describe('CI Badge Link Component', () => {
- let CIBadge;
- let vm;
+ let wrapper;
const statuses = {
canceled: {
@@ -72,29 +71,30 @@ describe('CI Badge Link Component', () => {
},
};
- beforeEach(() => {
- CIBadge = Vue.extend(ciBadge);
- });
+ const findIcon = () => wrapper.findComponent(CiIcon);
+
+ const createComponent = (propsData) => {
+ wrapper = shallowMount(CiBadge, { propsData });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- it('should render each status badge', () => {
- Object.keys(statuses).map((status) => {
- vm = mountComponent(CIBadge, { status: statuses[status] });
+ it.each(Object.keys(statuses))('should render badge for status: %s', (status) => {
+ createComponent({ status: statuses[status] });
- expect(vm.$el.getAttribute('href')).toEqual(statuses[status].details_path);
- expect(vm.$el.textContent.trim()).toEqual(statuses[status].text);
- expect(vm.$el.getAttribute('class')).toContain(`ci-status ci-${statuses[status].group}`);
- expect(vm.$el.querySelector('svg')).toBeDefined();
- return vm;
- });
+ expect(wrapper.attributes('href')).toBe(statuses[status].details_path);
+ expect(wrapper.text()).toBe(statuses[status].text);
+ expect(wrapper.classes()).toContain('ci-status');
+ expect(wrapper.classes()).toContain(`ci-${statuses[status].group}`);
+ expect(findIcon().exists()).toBe(true);
});
it('should not render label', () => {
- vm = mountComponent(CIBadge, { status: statuses.canceled, showText: false });
+ createComponent({ status: statuses.canceled, showText: false });
- expect(vm.$el.textContent.trim()).toEqual('');
+ expect(wrapper.text()).toBe('');
});
});
diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js
index 63afe631063..6d52db7ae65 100644
--- a/spec/frontend/vue_shared/components/ci_icon_spec.js
+++ b/spec/frontend/vue_shared/components/ci_icon_spec.js
@@ -1,122 +1,51 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
describe('CI Icon component', () => {
- const Component = Vue.extend(ciIcon);
- let vm;
+ let wrapper;
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('should render a span element with an svg', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_success',
- },
- });
-
- expect(vm.$el.tagName).toEqual('SPAN');
- expect(vm.$el.querySelector('span > svg')).toBeDefined();
- });
-
- it('should render a success status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_success',
- group: 'success',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-success')).toEqual(true);
- });
-
- it('should render a failed status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_failed',
- group: 'failed',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-failed')).toEqual(true);
- });
-
- it('should render success with warnings status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_warning',
- group: 'warning',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-warning')).toEqual(true);
- });
-
- it('should render pending status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_pending',
- group: 'pending',
- },
+ wrapper = shallowMount(ciIcon, {
+ propsData: {
+ status: {
+ icon: 'status_success',
+ },
+ },
+ });
+
+ expect(wrapper.find('span').exists()).toBe(true);
+ expect(wrapper.find(GlIcon).exists()).toBe(true);
+ });
+
+ describe('rendering a status', () => {
+ it.each`
+ icon | group | cssClass
+ ${'status_success'} | ${'success'} | ${'ci-status-icon-success'}
+ ${'status_failed'} | ${'failed'} | ${'ci-status-icon-failed'}
+ ${'status_warning'} | ${'warning'} | ${'ci-status-icon-warning'}
+ ${'status_pending'} | ${'pending'} | ${'ci-status-icon-pending'}
+ ${'status_running'} | ${'running'} | ${'ci-status-icon-running'}
+ ${'status_created'} | ${'created'} | ${'ci-status-icon-created'}
+ ${'status_skipped'} | ${'skipped'} | ${'ci-status-icon-skipped'}
+ ${'status_canceled'} | ${'canceled'} | ${'ci-status-icon-canceled'}
+ ${'status_manual'} | ${'manual'} | ${'ci-status-icon-manual'}
+ `('should render a $group status', ({ icon, group, cssClass }) => {
+ wrapper = shallowMount(ciIcon, {
+ propsData: {
+ status: {
+ icon,
+ group,
+ },
+ },
+ });
+
+ expect(wrapper.classes()).toContain(cssClass);
});
-
- expect(vm.$el.classList.contains('ci-status-icon-pending')).toEqual(true);
- });
-
- it('should render running status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_running',
- group: 'running',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-running')).toEqual(true);
- });
-
- it('should render created status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_created',
- group: 'created',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-created')).toEqual(true);
- });
-
- it('should render skipped status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_skipped',
- group: 'skipped',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-skipped')).toEqual(true);
- });
-
- it('should render canceled status', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_canceled',
- group: 'canceled',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-canceled')).toEqual(true);
- });
-
- it('should render status for manual action', () => {
- vm = mountComponent(Component, {
- status: {
- icon: 'status_manual',
- group: 'manual',
- },
- });
-
- expect(vm.$el.classList.contains('ci-status-icon-manual')).toEqual(true);
});
});
diff --git a/spec/frontend/vue_shared/components/clipboard_button_spec.js b/spec/frontend/vue_shared/components/clipboard_button_spec.js
index 0d4266ce82f..ab4008484e5 100644
--- a/spec/frontend/vue_shared/components/clipboard_button_spec.js
+++ b/spec/frontend/vue_shared/components/clipboard_button_spec.js
@@ -1,7 +1,7 @@
-import { mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { mount } from '@vue/test-utils';
import initCopyToClipboard from '~/behaviors/copy_to_clipboard';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('clipboard button', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/clone_dropdown_spec.js b/spec/frontend/vue_shared/components/clone_dropdown_spec.js
index 5b8576ad761..eefd1838988 100644
--- a/spec/frontend/vue_shared/components/clone_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/clone_dropdown_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlFormInputGroup, GlDropdownSectionHeader } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import CloneDropdown from '~/vue_shared/components/clone_dropdown.vue';
describe('Clone Dropdown Button', () => {
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 c8fe6c3131c..d30f36ec63c 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
@@ -13,6 +13,7 @@ describe('ColorPicker', () => {
};
const setColor = '#000000';
+ const invalidText = 'Please enter a valid hex (#RRGGBB or #RGB) color value';
const label = () => wrapper.find(GlFormGroup).attributes('label');
const colorPreview = () => wrapper.find('[data-testid="color-preview"]');
const colorPicker = () => wrapper.find(GlFormInput);
@@ -28,8 +29,6 @@ describe('ColorPicker', () => {
'#428BCA': 'Moderate blue',
'#44AD8E': 'Lime green',
};
-
- createComponent(shallowMount);
});
afterEach(() => {
@@ -38,6 +37,8 @@ describe('ColorPicker', () => {
describe('label', () => {
it('hides the label if the label is not passed', () => {
+ createComponent(shallowMount);
+
expect(label()).toBe('');
});
@@ -55,43 +56,37 @@ describe('ColorPicker', () => {
expect(colorPreview().attributes('style')).toBe(undefined);
expect(colorPicker().attributes('value')).toBe(undefined);
expect(colorInput().props('value')).toBe('');
+ expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-gray-400');
});
it('has a color set on initialization', () => {
- createComponent(shallowMount, { setColor });
+ createComponent(mount, { value: setColor });
- expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ expect(colorInput().props('value')).toBe(setColor);
});
it('emits input event from component when a color is selected', async () => {
createComponent();
await colorInput().setValue(setColor);
- expect(wrapper.emitted().input[0]).toEqual([setColor]);
+ expect(wrapper.emitted().input[0]).toStrictEqual([setColor]);
});
it('trims spaces from submitted colors', async () => {
createComponent();
await colorInput().setValue(` ${setColor} `);
- expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ expect(wrapper.emitted().input[0]).toStrictEqual([setColor]);
+ expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-gray-400');
+ expect(colorInput().attributes('class')).not.toContain('is-invalid');
});
- it('shows invalid feedback when an invalid color is used', async () => {
- createComponent();
- await colorInput().setValue('abcd');
-
- expect(invalidFeedback().text()).toBe(
- 'Please enter a valid hex (#RRGGBB or #RGB) color value',
- );
- expect(wrapper.emitted().input).toBe(undefined);
- });
-
- it('shows an invalid feedback border on the preview when an invalid color is used', async () => {
- createComponent();
- await colorInput().setValue('abcd');
+ it('shows invalid feedback when the state is marked as invalid', async () => {
+ createComponent(mount, { invalidFeedback: invalidText, state: false });
+ expect(invalidFeedback().text()).toBe(invalidText);
expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-red-500');
+ expect(colorInput().attributes('class')).toContain('is-invalid');
});
});
@@ -100,14 +95,14 @@ describe('ColorPicker', () => {
createComponent();
await colorInput().setValue(setColor);
- expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ expect(wrapper.emitted().input[0]).toStrictEqual([setColor]);
});
it('has color picker value entered', async () => {
createComponent();
await colorPicker().setValue(setColor);
- expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ expect(wrapper.emitted().input[0]).toStrictEqual([setColor]);
});
});
@@ -132,7 +127,7 @@ describe('ColorPicker', () => {
createComponent();
await presetColors().at(0).trigger('click');
- expect(wrapper.vm.$data.selectedColor).toBe(setColor);
+ expect(wrapper.emitted().input[0]).toStrictEqual([setColor]);
});
});
});
diff --git a/spec/frontend/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js
index 6f3c97f7194..66ceebed489 100644
--- a/spec/frontend/vue_shared/components/commit_spec.js
+++ b/spec/frontend/vue_shared/components/commit_spec.js
@@ -1,5 +1,6 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CommitComponent from '~/vue_shared/components/commit.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -13,11 +14,14 @@ describe('Commit component', () => {
};
const findUserAvatar = () => wrapper.find(UserAvatarLink);
+ const findRefName = () => wrapper.findByTestId('ref-name');
const createComponent = (propsData) => {
- wrapper = shallowMount(CommitComponent, {
- propsData,
- });
+ wrapper = extendedWrapper(
+ shallowMount(CommitComponent, {
+ propsData,
+ }),
+ );
};
afterEach(() => {
@@ -223,4 +227,20 @@ describe('Commit component', () => {
expect(wrapper.find('.ref-name').exists()).toBe(false);
});
});
+
+ describe('When commitRef has a path property instead of ref_url property', () => {
+ it('should render path as href attribute', () => {
+ props = {
+ commitRef: {
+ name: 'master',
+ path: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ };
+
+ createComponent(props);
+
+ expect(findRefName().exists()).toBe(true);
+ expect(findRefName().attributes('href')).toBe(props.commitRef.path);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js
index 31e843297fa..af3b63ad7e5 100644
--- a/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js
@@ -33,4 +33,14 @@ describe('Image Viewer', () => {
},
);
});
+
+ describe('file path', () => {
+ it('should output a valid URL path for the image', () => {
+ wrapper = mount(ImageViewer, {
+ propsData: { path: '/url/hello#1.jpg' },
+ });
+
+ expect(wrapper.find('img').attributes('src')).toBe('/url/hello%231.jpg');
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
index 22ee6acfed8..3ffb23dc7a0 100644
--- a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
@@ -1,6 +1,6 @@
-import $ from 'jquery';
-import MockAdapter from 'axios-mock-adapter';
import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import MarkdownViewer from '~/vue_shared/components/content_viewer/viewers/markdown_viewer.vue';
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 b6bffbcc6f3..eacc41ccdad 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
@@ -1,6 +1,6 @@
+import { mount } from '@vue/test-utils';
import Vue from 'vue';
import { compileToFunctions } from 'vue-template-compiler';
-import { mount } from '@vue/test-utils';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
import imageDiffViewer from '~/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue';
diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
index e91e6577aaf..9f433816b34 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
@@ -1,6 +1,5 @@
-import Vuex from 'vuex';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
-import Renamed from '~/vue_shared/components/diff_viewer/viewers/renamed.vue';
+import Vuex from 'vuex';
import {
TRANSITION_LOAD_START,
TRANSITION_LOAD_ERROR,
@@ -10,6 +9,7 @@ import {
STATE_LOADING,
STATE_ERRORED,
} from '~/diffs/constants';
+import Renamed from '~/vue_shared/components/diff_viewer/viewers/renamed.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/vue_shared/components/dismissible_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_alert_spec.js
index 17905254292..cfa6d1064e5 100644
--- a/spec/frontend/vue_shared/components/dismissible_alert_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_alert_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import DismissibleAlert from '~/vue_shared/components/dismissible_alert.vue';
const TEST_HTML = 'Hello World! <strong>Foo</strong>';
diff --git a/spec/frontend/vue_shared/components/dismissible_container_spec.js b/spec/frontend/vue_shared/components/dismissible_container_spec.js
index 2c0e363fa0e..b8aeea38e77 100644
--- a/spec/frontend/vue_shared/components/dismissible_container_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_container_spec.js
@@ -1,5 +1,5 @@
-import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import dismissibleContainer from '~/vue_shared/components/dismissible_container.vue';
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 4c4baf23120..175d79dd1c2 100644
--- a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
@@ -1,5 +1,5 @@
-import { mount, shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import Component from '~/vue_shared/components/dismissible_feedback_alert.vue';
diff --git a/spec/frontend/vue_shared/components/editor_lite_spec.js b/spec/frontend/vue_shared/components/editor_lite_spec.js
index 70fdd8e24a5..badd5aed0e3 100644
--- a/spec/frontend/vue_shared/components/editor_lite_spec.js
+++ b/spec/frontend/vue_shared/components/editor_lite_spec.js
@@ -1,7 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import EditorLite from '~/vue_shared/components/editor_lite.vue';
+import { EDITOR_READY_EVENT } from '~/editor/constants';
import Editor from '~/editor/editor_lite';
+import EditorLite from '~/vue_shared/components/editor_lite.vue';
jest.mock('~/editor/editor_lite');
@@ -110,13 +111,13 @@ describe('Editor Lite component', () => {
expect(wrapper.emitted().input).toEqual([[value]]);
});
- it('emits editor-ready event when the Editor Lite is ready', async () => {
+ it('emits EDITOR_READY_EVENT event when the Editor Lite is ready', async () => {
const el = wrapper.find({ ref: 'editor' }).element;
- expect(wrapper.emitted()['editor-ready']).toBeUndefined();
+ expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeUndefined();
- await el.dispatchEvent(new Event('editor-ready'));
+ await el.dispatchEvent(new Event(EDITOR_READY_EVENT));
- expect(wrapper.emitted()['editor-ready']).toBeDefined();
+ expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeDefined();
});
it('component API `getEditor()` returns the editor instance', () => {
diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js
index 724405a109f..7874658cc0f 100644
--- a/spec/frontend/vue_shared/components/expand_button_spec.js
+++ b/spec/frontend/vue_shared/components/expand_button_spec.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
const text = {
diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js
index 238a5440664..d757b7fac72 100644
--- a/spec/frontend/vue_shared/components/file_finder/index_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js
@@ -1,9 +1,9 @@
-import Vue from 'vue';
import Mousetrap from 'mousetrap';
-import { file } from 'jest/ide/helpers';
+import Vue from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
+import { file } from 'jest/ide/helpers';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
+import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
describe('File finder item spec', () => {
const Component = Vue.extend(FindFileComponent);
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 c60e6335389..1a4a97efb95 100644
--- a/spec/frontend/vue_shared/components/file_finder/item_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/item_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { file } from 'jest/ide/helpers';
import createComponent from 'helpers/vue_mount_component_helper';
+import { file } from 'jest/ide/helpers';
import ItemComponent from '~/vue_shared/components/file_finder/item.vue';
describe('File finder item spec', () => {
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
index e55449dc684..c10663f6c14 100644
--- a/spec/frontend/vue_shared/components/file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
diff --git a/spec/frontend/vue_shared/components/file_row_spec.js b/spec/frontend/vue_shared/components/file_row_spec.js
index bd6a18bf704..62fb29c455c 100644
--- a/spec/frontend/vue_shared/components/file_row_spec.js
+++ b/spec/frontend/vue_shared/components/file_row_spec.js
@@ -1,10 +1,10 @@
-import { file } from 'jest/ide/helpers';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { file } from 'jest/ide/helpers';
+import { escapeFileUrl } from '~/lib/utils/url_utility';
+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';
-import FileIcon from '~/vue_shared/components/file_icon.vue';
-import { escapeFileUrl } from '~/lib/utils/url_utility';
describe('File row component', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/file_tree_spec.js b/spec/frontend/vue_shared/components/file_tree_spec.js
index 7a4982fd29b..39a7c7a2b3a 100644
--- a/spec/frontend/vue_shared/components/file_tree_spec.js
+++ b/spec/frontend/vue_shared/components/file_tree_spec.js
@@ -1,5 +1,5 @@
-import { pick } from 'lodash';
import { shallowMount } from '@vue/test-utils';
+import { pick } from 'lodash';
import FileTree from '~/vue_shared/components/file_tree.vue';
const MockFileRow = {
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 b58ce0083c0..9fa9d35e3e2 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
@@ -1,4 +1,3 @@
-import { shallowMount, mount } from '@vue/test-utils';
import {
GlFilteredSearch,
GlButtonGroup,
@@ -7,13 +6,13 @@ import {
GlDropdownItem,
GlFormCheckbox,
} from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
+import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
+import { SortDirection } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { uniqueTokens } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
-import { SortDirection } from '~/vue_shared/components/filtered_search_bar/constants';
-
-import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
-import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import {
mockAvailableTokens,
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 1dd5f08e76a..05bad572472 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
@@ -2,12 +2,12 @@ import axios from 'axios';
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 { deprecatedCreateFlash as createFlash } from '~/flash';
+import httpStatusCodes 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';
import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state';
-import httpStatusCodes from '~/lib/utils/http_status';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import Api from '~/api';
import { filterMilestones, filterUsers, filterLabels } from './mock_data';
const milestonesEndpoint = 'fake_milestones_endpoint';
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js
index 263a4ee178f..66c6267027b 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js
@@ -1,9 +1,9 @@
import { get } from 'lodash';
import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data';
-import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state';
-import mutations from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutations';
-import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types';
+import mutations from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutations';
+import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state';
import { filterMilestones, filterUsers, filterLabels } from './mock_data';
let state = null;
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
index 3997d6a99a6..765e576914c 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
@@ -1,15 +1,15 @@
-import { mount } from '@vue/test-utils';
import {
GlFilteredSearchToken,
GlFilteredSearchTokenSegment,
GlFilteredSearchSuggestion,
GlDropdownDivider,
} from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
DEFAULT_LABEL_NONE,
DEFAULT_LABEL_ANY,
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 35f487330be..a20bc4986fc 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
@@ -1,15 +1,15 @@
-import { mount } from '@vue/test-utils';
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
GlDropdownDivider,
} from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import {
DEFAULT_LABEL_NONE,
DEFAULT_LABEL_ANY,
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 dda0ad39bbc..7676ce10ce0 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
@@ -1,19 +1,19 @@
-import { mount } from '@vue/test-utils';
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
GlDropdownDivider,
} from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import {
mockRegularLabel,
mockLabels,
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
DEFAULT_LABELS,
DEFAULT_LABEL_NONE,
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 164561f6244..9f550ac9afc 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
@@ -1,15 +1,15 @@
-import { mount } from '@vue/test-utils';
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
GlDropdownDivider,
} from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import createFlash from '~/flash';
import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
index d8e6e37bb89..370b6eb01bc 100644
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
+++ b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
@@ -1,12 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `
-"raised_hands
- <gl-emoji
-
- data-name=\\"raised_hands\\"></gl-emoji>
- "
-`;
+exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `"raised_hands <gl-emoji data-name=\\"raised_hands\\"></gl-emoji>"`;
exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
@@ -21,10 +15,10 @@ exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1
exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = `
"
<div class=\\"gl-display-flex gl-align-items-center\\">
- <div class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-rounded-small
+ <div class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-rounded-small
gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\">
G</div>
- <div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\">
+ <div class=\\"gl-line-height-normal gl-ml-4\\">
<div>1-1s &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt; (2)</div>
<div class=\\"gl-text-gray-700\\">GitLab Support Team</div>
</div>
@@ -36,8 +30,8 @@ exports[`gfm_autocomplete/utils members config shows an avatar character, name,
exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = `
"
<div class=\\"gl-display-flex gl-align-items-center\\">
- <img class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
- <div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\">
+ <img class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
+ <div class=\\"gl-line-height-normal gl-ml-4\\">
<div>My Name &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;</div>
<div class=\\"gl-text-gray-700\\">@myusername</div>
</div>
diff --git a/spec/frontend/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js
index fcc5c0cd310..82d18c7fd3f 100644
--- a/spec/frontend/vue_shared/components/gl_countdown_spec.js
+++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js
@@ -1,5 +1,5 @@
-import mountComponent from 'helpers/vue_mount_component_helper';
import Vue from 'vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
describe('GlCountdown', () => {
diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
index 6802499ed52..390a70792f3 100644
--- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
@@ -1,6 +1,7 @@
+import { GlModal } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlModal } from '@gitlab/ui';
+import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue';
import createState from '~/vuex_shared/modules/modal/state';
@@ -129,7 +130,7 @@ describe('GlModalVuex', () => {
wrapper.vm
.$nextTick()
.then(() => {
- expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', TEST_MODAL_ID);
+ expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, TEST_MODAL_ID);
})
.then(done)
.catch(done.fail);
@@ -146,7 +147,7 @@ describe('GlModalVuex', () => {
wrapper.vm
.$nextTick()
.then(() => {
- expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', TEST_MODAL_ID);
+ expect(rootEmit).toHaveBeenCalledWith(BV_HIDE_MODAL, TEST_MODAL_ID);
})
.then(done)
.catch(done.fail);
diff --git a/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js b/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js
index 30afb044bbf..ac670b622b1 100644
--- a/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js
@@ -1,6 +1,6 @@
-import Vuex from 'vuex';
import { GlToggle } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import GlToggleVuex from '~/vue_shared/components/gl_toggle_vuex.vue';
const localVue = createLocalVue();
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 5233a64ce5e..b54d120b55b 100644
--- a/spec/frontend/vue_shared/components/header_ci_component_spec.js
+++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js
@@ -1,93 +1,103 @@
-import Vue from 'vue';
-import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
-import headerCi from '~/vue_shared/components/header_ci_component.vue';
+import { GlButton, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import CiIconBadge from '~/vue_shared/components/ci_badge_link.vue';
+import HeaderCi from '~/vue_shared/components/header_ci_component.vue';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
describe('Header CI Component', () => {
- let HeaderCi;
- let vm;
- let props;
-
- beforeEach(() => {
- HeaderCi = Vue.extend(headerCi);
- props = {
- status: {
- group: 'failed',
- icon: 'status_failed',
- label: 'failed',
- text: 'failed',
- details_path: 'path',
- },
- itemName: 'job',
- itemId: 123,
- time: '2017-05-08T14:57:39.781Z',
- user: {
- web_url: 'path',
- name: 'Foo',
- username: 'foobar',
- email: 'foo@bar.com',
- avatar_url: 'link',
- },
- hasSidebarButton: true,
- };
- });
+ let wrapper;
+
+ const defaultProps = {
+ status: {
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ },
+ itemName: 'job',
+ itemId: 123,
+ time: '2017-05-08T14:57:39.781Z',
+ user: {
+ web_url: 'path',
+ name: 'Foo',
+ username: 'foobar',
+ email: 'foo@bar.com',
+ avatar_url: 'link',
+ },
+ hasSidebarButton: true,
+ };
+
+ const findIconBadge = () => wrapper.findComponent(CiIconBadge);
+ const findTimeAgo = () => wrapper.findComponent(TimeagoTooltip);
+ const findUserLink = () => wrapper.findComponent(GlLink);
+ const findSidebarToggleBtn = () => wrapper.findComponent(GlButton);
+ const findActionButtons = () => wrapper.findByTestId('ci-header-action-buttons');
+ const findHeaderItemText = () => wrapper.findByTestId('ci-header-item-text');
+
+ const createComponent = (props, slots) => {
+ wrapper = extendedWrapper(
+ shallowMount(HeaderCi, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ ...slots,
+ }),
+ );
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- const findActionButtons = () => vm.$el.querySelector('[data-testid="headerButtons"]');
-
describe('render', () => {
beforeEach(() => {
- vm = mountComponent(HeaderCi, props);
+ createComponent();
});
it('should render status badge', () => {
- expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
- expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
- expect(vm.$el.querySelector('.ci-failed').getAttribute('href')).toEqual(
- props.status.details_path,
- );
+ expect(findIconBadge().exists()).toBe(true);
});
it('should render item name and id', () => {
- expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
+ expect(findHeaderItemText().text()).toBe('job #123');
});
it('should render timeago date', () => {
- expect(vm.$el.querySelector('time')).toBeDefined();
+ expect(findTimeAgo().exists()).toBe(true);
});
it('should render user icon and name', () => {
- expect(vm.$el.querySelector('.js-user-link').innerText.trim()).toContain(props.user.name);
+ expect(findUserLink().text()).toContain(defaultProps.user.name);
});
it('should render sidebar toggle button', () => {
- expect(vm.$el.querySelector('.js-sidebar-build-toggle')).not.toBeNull();
+ expect(findSidebarToggleBtn().exists()).toBe(true);
});
- it('should not render header action buttons when empty', () => {
- expect(findActionButtons()).toBeNull();
+ it('should not render header action buttons when slot is empty', () => {
+ expect(findActionButtons().exists()).toBe(false);
});
});
describe('slot', () => {
it('should render header action buttons', () => {
- vm = mountComponentWithSlots(HeaderCi, { props, slots: { default: 'Test Actions' } });
-
- const buttons = findActionButtons();
+ createComponent({}, { slots: { default: 'Test Actions' } });
- expect(buttons).not.toBeNull();
- expect(buttons.textContent).toEqual('Test Actions');
+ expect(findActionButtons().exists()).toBe(true);
+ expect(findActionButtons().text()).toBe('Test Actions');
});
});
describe('shouldRenderTriggeredLabel', () => {
- it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => {
- vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false });
+ it('should render created keyword when the shouldRenderTriggeredLabel is false', () => {
+ createComponent({ shouldRenderTriggeredLabel: false });
- expect(vm.$el.textContent).toContain('created');
- expect(vm.$el.textContent).not.toContain('triggered');
+ expect(wrapper.text()).toContain('created');
+ expect(wrapper.text()).not.toContain('triggered');
});
});
});
diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js
new file mode 100644
index 00000000000..baf80a8a04e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/help_popover_spec.js
@@ -0,0 +1,65 @@
+import { GlButton, GlPopover } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import HelpPopover from '~/vue_shared/components/help_popover.vue';
+
+describe('HelpPopover', () => {
+ let wrapper;
+ const title = 'popover <strong>title</strong>';
+ const content = 'popover <b>content</b>';
+
+ const findQuestionButton = () => wrapper.find(GlButton);
+ const findPopover = () => wrapper.find(GlPopover);
+ const buildWrapper = (options = {}) => {
+ wrapper = mount(HelpPopover, {
+ propsData: {
+ options: {
+ title,
+ content,
+ ...options,
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders a link button with an icon question', () => {
+ expect(findQuestionButton().props()).toMatchObject({
+ icon: 'question',
+ variant: 'link',
+ });
+ expect(findQuestionButton().attributes().tabindex).toBe('0');
+ });
+
+ it('renders popover that uses the question button as target', () => {
+ expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el);
+ });
+
+ it('triggers popover on hover and focus', () => {
+ expect(findPopover().props().triggers).toBe('hover focus');
+ });
+
+ it('allows rendering title with HTML tags', () => {
+ expect(findPopover().find('strong').exists()).toBe(true);
+ });
+
+ it('allows rendering content with HTML tags', () => {
+ expect(findPopover().find('b').exists()).toBe(true);
+ });
+
+ it('binds other popover options to the popover instance', () => {
+ const placement = 'bottom';
+
+ wrapper.destroy();
+ buildWrapper({ placement });
+
+ expect(findPopover().props().placement).toBe(placement);
+ });
+});
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 4269d36d0e2..c0e8b719007 100644
--- a/spec/frontend/vue_shared/components/integration_help_text_spec.js
+++ b/spec/frontend/vue_shared/components/integration_help_text_spec.js
@@ -1,6 +1,6 @@
+import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue';
describe('IntegrationHelpText component', () => {
diff --git a/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js b/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js
index a03a3915e1b..573501233b9 100644
--- a/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js
+++ b/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js
@@ -1,8 +1,8 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue';
-import createIssueStore from '~/notes/stores';
import { createStore as createMrStore } from '~/mr_notes/stores';
+import createIssueStore from '~/notes/stores';
+import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue';
const ISSUABLE_TYPE_ISSUE = 'issue';
const ISSUABLE_TYPE_MR = 'merge request';
diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
index 5f614bfc751..5c29c267c99 100644
--- a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { mockAssigneesList } from 'jest/boards/mock_data';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
const TEST_CSS_CLASSES = 'test-classes';
const TEST_MAX_VISIBLE = 4;
diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
index ffcb891c4fc..9a121050225 100644
--- a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
@@ -1,8 +1,8 @@
-import Vue from 'vue';
+import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import { mockMilestone } from 'jest/boards/mock_data';
-import { GlIcon } from '@gitlab/ui';
import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
const createComponent = (milestone = mockMilestone) => {
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
index 3dc34583118..f34a2db0851 100644
--- a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
@@ -1,8 +1,8 @@
import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
+import IssueDueDate from '~/boards/components/issue_due_date.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
-import IssueDueDate from '~/boards/components/issue_due_date.vue';
import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data';
describe('RelatedIssuableItem', () => {
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 b9f0d88548d..c56628fcbcd 100644
--- a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlFormTextarea, GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import ApplySuggestionComponent from '~/vue_shared/components/markdown/apply_suggestion.vue';
describe('Apply Suggestion component', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index a2ce6f40193..442032840e1 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -1,9 +1,9 @@
import { mount } from '@vue/test-utils';
-import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
-import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
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 80cf1f655c6..be1d840dd29 100644
--- a/spec/frontend/vue_shared/components/markdown/field_view_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_view_spec.js
@@ -1,5 +1,5 @@
-import $ from 'jquery';
import { shallowMount } from '@vue/test-utils';
+import $ from 'jquery';
import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue';
diff --git a/spec/frontend/vue_shared/components/memory_graph_spec.js b/spec/frontend/vue_shared/components/memory_graph_spec.js
index 9a5ee544d8f..53b96bd1b98 100644
--- a/spec/frontend/vue_shared/components/memory_graph_spec.js
+++ b/spec/frontend/vue_shared/components/memory_graph_spec.js
@@ -1,6 +1,6 @@
-import Vue from 'vue';
-import { shallowMount } from '@vue/test-utils';
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', () => {
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 ca9f8ff54d4..adb72c3ef85 100644
--- a/spec/frontend/vue_shared/components/modal_copy_button_spec.js
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -1,4 +1,5 @@
import { shallowMount, createWrapper } from '@vue/test-utils';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
describe('modal copy button', () => {
@@ -31,7 +32,7 @@ describe('modal copy button', () => {
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().success).not.toBeEmpty();
expect(document.execCommand).toHaveBeenCalledWith('copy');
- expect(root.emitted('bv::hide::tooltip')).toEqual([['test-id']]);
+ expect(root.emitted(BV_HIDE_TOOLTIP)).toEqual([['test-id']]);
});
});
it("should propagate the clipboard error event if execCommand doesn't work", () => {
diff --git a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
index 233c488b60b..99671f1ffb7 100644
--- a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
+import { shallowMount } from '@vue/test-utils';
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
describe('MultiSelectDropdown Component', () => {
diff --git a/spec/frontend/vue_shared/components/navigation_tabs_spec.js b/spec/frontend/vue_shared/components/navigation_tabs_spec.js
index b1119bfb150..30a89fed12f 100644
--- a/spec/frontend/vue_shared/components/navigation_tabs_spec.js
+++ b/spec/frontend/vue_shared/components/navigation_tabs_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
describe('navigation tabs component', () => {
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 cc9f05beb06..835759b1f20 100644
--- a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
+++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
describe('Issue Warning Component', () => {
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 d5eac7c2aa3..48dacc50923 100644
--- a/spec/frontend/vue_shared/components/notes/system_note_spec.js
+++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
-import IssueSystemNote from '~/vue_shared/components/notes/system_note.vue';
-import createStore from '~/notes/stores';
import initMRPopovers from '~/mr_popover/index';
+import createStore from '~/notes/stores';
+import IssueSystemNote from '~/vue_shared/components/notes/system_note.vue';
jest.mock('~/mr_popover/index', () => jest.fn());
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 491f783622a..74e9cbcbb53 100644
--- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
+++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
@@ -1,9 +1,9 @@
-import { mount } from '@vue/test-utils';
import { GlAlert, GlBadge, GlPagination, GlTabs, GlTab } from '@gitlab/ui';
-import PageWrapper from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue';
+import { mount } from '@vue/test-utils';
+import Tracking from '~/tracking';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
-import Tracking from '~/tracking';
+import PageWrapper from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue';
import mockItems from './mocks/items.json';
import mockFilters from './mocks/items_filters.json';
diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js
index ad82aee0098..83f1e2844f9 100644
--- a/spec/frontend/vue_shared/components/pagination_links_spec.js
+++ b/spec/frontend/vue_shared/components/pagination_links_spec.js
@@ -1,6 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlPagination } from '@gitlab/ui';
-import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
+import { mount } from '@vue/test-utils';
import {
PREV,
NEXT,
@@ -9,6 +8,7 @@ import {
LABEL_NEXT_PAGE,
LABEL_LAST_PAGE,
} from '~/vue_shared/components/pagination/constants';
+import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
describe('Pagination links component', () => {
const pageInfo = {
diff --git a/spec/frontend/vue_shared/components/pikaday_spec.js b/spec/frontend/vue_shared/components/pikaday_spec.js
index 1c6876c282c..fed4ce5e696 100644
--- a/spec/frontend/vue_shared/components/pikaday_spec.js
+++ b/spec/frontend/vue_shared/components/pikaday_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDatepicker } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import datePicker from '~/vue_shared/components/pikaday.vue';
describe('datePicker', () => {
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 016622fd0bb..06b00a8e196 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,8 +1,7 @@
-import Vue from 'vue';
-import { head } from 'lodash';
-
import { GlSearchBoxByType, GlInfiniteScroll } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
+import { head } from 'lodash';
+import Vue from 'vue';
import { trimText } from 'helpers/text_helper';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue';
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 84c738764a3..4ec608aaf07 100644
--- a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js
+++ b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import Tracking from '~/tracking';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
describe('Package code instruction', () => {
let wrapper;
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 09dacfae363..3134e0d3e21 100644
--- a/spec/frontend/vue_shared/components/registry/details_row_spec.js
+++ b/spec/frontend/vue_shared/components/registry/details_row_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import component from '~/vue_shared/components/registry/details_row.vue';
describe('DetailsRow', () => {
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 d51ddda2e3e..f146f87342f 100644
--- a/spec/frontend/vue_shared/components/registry/history_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/history_item_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import component from '~/vue_shared/components/registry/history_item.vue';
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 3d3cfbe13e3..1ccf3ddc5a5 100644
--- a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import component from '~/vue_shared/components/registry/metadata_item.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
diff --git a/spec/frontend/vue_shared/components/registry/registry_search_spec.js b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
new file mode 100644
index 00000000000..28bdb275756
--- /dev/null
+++ b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
@@ -0,0 +1,105 @@
+import { GlSorting, GlSortingItem, GlFilteredSearch } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/vue_shared/components/registry/registry_search.vue';
+
+describe('Registry Search', () => {
+ let wrapper;
+
+ const findPackageListSorting = () => wrapper.find(GlSorting);
+ const findSortingItems = () => wrapper.findAll(GlSortingItem);
+ const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
+
+ const defaultProps = {
+ filter: [],
+ sorting: { sort: 'asc', orderBy: 'name' },
+ tokens: ['foo'],
+ sortableFields: [{ label: 'name', orderBy: 'name' }, { label: 'baz' }],
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ stubs: {
+ GlSortingItem,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('searching', () => {
+ it('has a filtered-search component', () => {
+ mountComponent();
+
+ expect(findFilteredSearch().exists()).toBe(true);
+ });
+
+ it('binds the correct props to filtered-search', () => {
+ mountComponent();
+
+ expect(findFilteredSearch().props()).toMatchObject({
+ value: [],
+ placeholder: 'Filter results',
+ availableTokens: wrapper.vm.tokens,
+ });
+ });
+
+ it('emits filter:changed when value changes', () => {
+ mountComponent();
+
+ findFilteredSearch().vm.$emit('input', 'foo');
+
+ expect(wrapper.emitted('filter:changed')).toEqual([['foo']]);
+ });
+
+ it('emits filter:submit on submit event', () => {
+ mountComponent();
+
+ findFilteredSearch().vm.$emit('submit');
+ expect(wrapper.emitted('filter:submit')).toEqual([[]]);
+ });
+
+ it('emits filter:changed and filter:submit on clear event', () => {
+ mountComponent();
+
+ findFilteredSearch().vm.$emit('clear');
+
+ expect(wrapper.emitted('filter:changed')).toEqual([[[]]]);
+ expect(wrapper.emitted('filter:submit')).toEqual([[]]);
+ });
+
+ it('binds tokens prop', () => {
+ mountComponent();
+
+ expect(findFilteredSearch().props('availableTokens')).toEqual(defaultProps.tokens);
+ });
+ });
+
+ describe('sorting', () => {
+ it('has all the sortable items', () => {
+ mountComponent();
+
+ expect(findSortingItems()).toHaveLength(defaultProps.sortableFields.length);
+ });
+
+ it('on sort change emits sorting:changed event', () => {
+ mountComponent();
+
+ findPackageListSorting().vm.$emit('sortDirectionChange');
+ expect(wrapper.emitted('sorting:changed')).toEqual([[{ sort: 'desc' }]]);
+ });
+
+ it('on sort item click emits sorting:changed event ', () => {
+ mountComponent();
+
+ findSortingItems().at(0).vm.$emit('click');
+
+ expect(wrapper.emitted('sorting:changed')).toEqual([
+ [{ orderBy: defaultProps.sortableFields[0].orderBy }],
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
index 3a5514ef318..1fce3c5d0b0 100644
--- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
+++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
@@ -1,6 +1,6 @@
-import Vue from 'vue';
import { mount } from '@vue/test-utils';
import $ from 'jquery';
+import Vue from 'vue';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
jest.mock('~/lib/utils/common_utils', () => ({
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
index 51619cd9578..ce2b0d1ddc1 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
@@ -1,3 +1,5 @@
+import buildCustomRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
+import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
import {
generateToolbarItem,
addCustomEventListener,
@@ -8,8 +10,6 @@ import {
getMarkdown,
getEditorOptions,
} from '~/vue_shared/components/rich_content_editor/services/editor_service';
-import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
-import buildCustomRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
import sanitizeHTML from '~/vue_shared/components/rich_content_editor/services/sanitize_html';
jest.mock('~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer');
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
index 16370a7aaad..97aecda97d2 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal, GlTabs } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { IMAGE_TABS } from '~/vue_shared/components/rich_content_editor/constants';
import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
import UploadImageTab from '~/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue';
-import { IMAGE_TABS } from '~/vue_shared/components/rich_content_editor/constants';
describe('Add Image Modal', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js
index d59d4cc1de9..3e9eaf58181 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue';
describe('Insert Video Modal', () => {
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js
index b9b93b274d2..47b1abd2ad2 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js
@@ -1,6 +1,6 @@
import Editor from '@toast-ui/editor';
-import { registerHTMLToMarkdownRenderer } from '~/vue_shared/components/rich_content_editor/services/editor_service';
import buildMarkdownToHTMLRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
+import { registerHTMLToMarkdownRenderer } from '~/vue_shared/components/rich_content_editor/services/editor_service';
describe('vue_shared/components/rich_content_editor', () => {
let editor;
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
index 2eb353a1801..8eb880b3984 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
@@ -1,14 +1,14 @@
-import { shallowMount } from '@vue/test-utils';
import { Editor, mockEditorApi } from '@toast-ui/vue-editor';
-import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
-import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
-import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue';
+import { shallowMount } from '@vue/test-utils';
import {
EDITOR_TYPES,
EDITOR_HEIGHT,
EDITOR_PREVIEW_STYLE,
CUSTOM_EVENTS,
} from '~/vue_shared/components/rich_content_editor/constants';
+import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
+import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue';
+import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import {
addCustomEventListener,
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
index d6bb01259bb..c1aaed6f0c3 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
@@ -1,5 +1,5 @@
-import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline';
import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline';
import { normalTextNode } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
index b31684a400e..234f6a4d4ca 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
@@ -1,5 +1,5 @@
-import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block';
import { buildUneditableHtmlAsTextTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block';
describe('rich_content_editor/services/renderers/render_html_block', () => {
const htmlBlockNode = {
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
index 521885f5687..425d0f41bcd 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
@@ -1,5 +1,5 @@
-import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text';
import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text';
import { buildMockTextNode, normalTextNode } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js
index 774f830f421..7c1809c290c 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js
@@ -1,15 +1,14 @@
import {
+ buildUneditableBlockTokens,
+ buildUneditableOpenTokens,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+import {
renderUneditableLeaf,
renderUneditableBranch,
renderWithAttributeDefinitions,
willAlwaysRender,
} from '~/vue_shared/components/rich_content_editor/services/renderers/render_utils';
-import {
- buildUneditableBlockTokens,
- buildUneditableOpenTokens,
-} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
-
import { originToken, uneditableCloseToken, attributeDefinition } from './mock_data';
describe('rich_content_editor/renderers/render_utils', () => {
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js
index 0e6f951bd53..5a56b499769 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js
@@ -1,6 +1,6 @@
+import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { GlIcon } from '@gitlab/ui';
import ToolbarItem from '~/vue_shared/components/rich_content_editor/toolbar_item.vue';
describe('Toolbar Item', () => {
diff --git a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js
new file mode 100644
index 00000000000..01f7f3d49c7
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js
@@ -0,0 +1,107 @@
+export const mockGraphqlRunnerPlatforms = {
+ data: {
+ runnerPlatforms: {
+ nodes: [
+ {
+ name: 'linux',
+ humanReadableName: 'Linux',
+ architectures: {
+ nodes: [
+ {
+ name: 'amd64',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64',
+ __typename: 'RunnerArchitecture',
+ },
+ {
+ name: '386',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386',
+ __typename: 'RunnerArchitecture',
+ },
+ {
+ name: 'arm',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm',
+ __typename: 'RunnerArchitecture',
+ },
+ {
+ name: 'arm64',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64',
+ __typename: 'RunnerArchitecture',
+ },
+ ],
+ __typename: 'RunnerArchitectureConnection',
+ },
+ __typename: 'RunnerPlatform',
+ },
+ {
+ name: 'osx',
+ humanReadableName: 'macOS',
+ architectures: {
+ nodes: [
+ {
+ name: 'amd64',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64',
+ __typename: 'RunnerArchitecture',
+ },
+ ],
+ __typename: 'RunnerArchitectureConnection',
+ },
+ __typename: 'RunnerPlatform',
+ },
+ {
+ name: 'windows',
+ humanReadableName: 'Windows',
+ architectures: {
+ nodes: [
+ {
+ name: 'amd64',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe',
+ __typename: 'RunnerArchitecture',
+ },
+ {
+ name: '386',
+ downloadLocation:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe',
+ __typename: 'RunnerArchitecture',
+ },
+ ],
+ __typename: 'RunnerArchitectureConnection',
+ },
+ __typename: 'RunnerPlatform',
+ },
+ {
+ name: 'docker',
+ humanReadableName: 'Docker',
+ architectures: null,
+ __typename: 'RunnerPlatform',
+ },
+ {
+ name: 'kubernetes',
+ humanReadableName: 'Kubernetes',
+ architectures: null,
+ __typename: 'RunnerPlatform',
+ },
+ ],
+ __typename: 'RunnerPlatformConnection',
+ },
+ project: { id: 'gid://gitlab/Project/1', __typename: 'Project' },
+ group: null,
+ },
+};
+
+export const mockGraphqlInstructions = {
+ data: {
+ runnerSetup: {
+ installInstructions:
+ "# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n",
+ registerInstructions:
+ 'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz',
+ __typename: 'RunnerSetup',
+ },
+ },
+};
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
new file mode 100644
index 00000000000..48db60bfd33
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
@@ -0,0 +1,113 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql';
+import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql';
+import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
+
+import { mockGraphqlRunnerPlatforms, mockGraphqlInstructions } from './mock_data';
+
+const projectPath = 'gitlab-org/gitlab';
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('RunnerInstructions component', () => {
+ let wrapper;
+ let fakeApollo;
+
+ const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]');
+ const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]');
+ const findArchitectureDropdownItems = () =>
+ wrapper.findAll('[data-testid="architecture-dropdown-item"]');
+ const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]');
+ const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]');
+
+ beforeEach(async () => {
+ const requestHandlers = [
+ [getRunnerPlatforms, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)],
+ [getRunnerSetupInstructions, jest.fn().mockResolvedValue(mockGraphqlInstructions)],
+ ];
+
+ fakeApollo = createMockApollo(requestHandlers);
+
+ wrapper = shallowMount(RunnerInstructions, {
+ provide: {
+ projectPath,
+ },
+ localVue,
+ apolloProvider: fakeApollo,
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should show the "Show Runner installation instructions" button', () => {
+ const button = findModalButton();
+
+ expect(button.exists()).toBe(true);
+ expect(button.text()).toBe('Show Runner installation instructions');
+ });
+
+ it('should contain a number of platforms buttons', () => {
+ const buttons = findPlatformButtons();
+
+ expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
+ });
+
+ it('should contain a number of dropdown items for the architecture options', () => {
+ const platformButton = findPlatformButtons().at(0);
+ platformButton.vm.$emit('click');
+
+ return wrapper.vm.$nextTick(() => {
+ const dropdownItems = findArchitectureDropdownItems();
+
+ expect(dropdownItems).toHaveLength(
+ mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
+ );
+ });
+ });
+
+ it('should display the binary installation instructions for a selected architecture', async () => {
+ const platformButton = findPlatformButtons().at(0);
+ platformButton.vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ const dropdownItem = findArchitectureDropdownItems().at(0);
+ dropdownItem.vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ const runner = findBinaryInstructionsSection();
+
+ expect(runner.text()).toMatch('sudo chmod +x /usr/local/bin/gitlab-runner');
+ expect(runner.text()).toMatch(
+ `sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`,
+ );
+ expect(runner.text()).toMatch(
+ 'sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner',
+ );
+ expect(runner.text()).toMatch('sudo gitlab-runner start');
+ });
+
+ it('should display the runner register instructions for a selected architecture', async () => {
+ const platformButton = findPlatformButtons().at(0);
+ platformButton.vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ const dropdownItem = findArchitectureDropdownItems().at(0);
+ dropdownItem.vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ const runner = findRunnerInstructionsSection();
+
+ expect(runner.text()).toMatch(mockGraphqlInstructions.data.runnerSetup.registerInstructions);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap b/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap
new file mode 100644
index 00000000000..51b8aa162bc
--- /dev/null
+++ b/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Settings Block renders the correct markup 1`] = `
+<section
+ class="settings no-animate"
+>
+ <div
+ class="settings-header"
+ >
+ <h4>
+ <div
+ data-testid="title-slot"
+ />
+ </h4>
+
+ <gl-button-stub
+ buttontextclasses=""
+ category="primary"
+ icon=""
+ size="medium"
+ variant="default"
+ >
+
+ Expand
+
+ </gl-button-stub>
+
+ <p>
+ <div
+ data-testid="description-slot"
+ />
+ </p>
+ </div>
+
+ <div
+ class="settings-content"
+ >
+ <div
+ data-testid="default-slot"
+ />
+ </div>
+</section>
+`;
diff --git a/spec/frontend/vue_shared/components/settings/settings_block_spec.js b/spec/frontend/vue_shared/components/settings/settings_block_spec.js
new file mode 100644
index 00000000000..2db0b001b5b
--- /dev/null
+++ b/spec/frontend/vue_shared/components/settings/settings_block_spec.js
@@ -0,0 +1,86 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/vue_shared/components/settings/settings_block.vue';
+
+describe('Settings Block', () => {
+ let wrapper;
+
+ const mountComponent = (propsData) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ slots: {
+ title: '<div data-testid="title-slot"></div>',
+ description: '<div data-testid="description-slot"></div>',
+ default: '<div data-testid="default-slot"></div>',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
+ const findTitleSlot = () => wrapper.find('[data-testid="title-slot"]');
+ const findDescriptionSlot = () => wrapper.find('[data-testid="description-slot"]');
+ const findExpandButton = () => wrapper.find(GlButton);
+
+ it('renders the correct markup', () => {
+ mountComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('has a default slot', () => {
+ mountComponent();
+
+ expect(findDefaultSlot().exists()).toBe(true);
+ });
+
+ it('has a title slot', () => {
+ mountComponent();
+
+ expect(findTitleSlot().exists()).toBe(true);
+ });
+
+ it('has a description slot', () => {
+ mountComponent();
+
+ expect(findDescriptionSlot().exists()).toBe(true);
+ });
+
+ describe('expanded behaviour', () => {
+ it('is collapsed by default', () => {
+ mountComponent();
+
+ expect(wrapper.classes('expanded')).toBe(false);
+ });
+
+ it('adds expanded class when the expand button is clicked', async () => {
+ mountComponent();
+
+ expect(wrapper.classes('expanded')).toBe(false);
+ expect(findExpandButton().text()).toBe('Expand');
+
+ await findExpandButton().vm.$emit('click');
+
+ expect(wrapper.classes('expanded')).toBe(true);
+ expect(findExpandButton().text()).toBe('Collapse');
+ });
+
+ it('is expanded when `defaultExpanded` is true no matter what', async () => {
+ mountComponent({ defaultExpanded: true });
+
+ expect(wrapper.classes('expanded')).toBe(true);
+
+ await findExpandButton().vm.$emit('click');
+
+ expect(wrapper.classes('expanded')).toBe(true);
+
+ await findExpandButton().vm.$emit('click');
+
+ expect(wrapper.classes('expanded')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
index fc1fa3fc1c1..3221e88192b 100644
--- a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
-import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
import DatePicker from '~/vue_shared/components/pikaday.vue';
+import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
describe('SidebarDatePicker', () => {
let wrapper;
diff --git a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
index 256b3cff525..a5a099d803a 100644
--- a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
@@ -1,5 +1,3 @@
-import { shallowMount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
import {
GlIcon,
GlLoadingIcon,
@@ -9,6 +7,8 @@ import {
GlSearchBoxByType,
GlButton,
} from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableMoveDropdown from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
index a55ad37c498..68ea94e72ce 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -1,6 +1,6 @@
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
-import { shallowMount } from '@vue/test-utils';
import LabelsSelect from '~/labels_select';
import BaseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
index 4b4d265800b..322e632da02 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -79,7 +79,7 @@ describe('DropdownCreateLabelComponent', () => {
const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0];
expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0].colorCode);
- expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);');
+ expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 153, 102);');
});
it('renders color input element', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
index 5cbbb99eaef..30dd92b72a4 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue';
const createComponent = (canEdit = true) =>
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
index ecb3c3a42c8..37f59c108df 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlLabel } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import DropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
import { mockConfig, mockLabels } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js
index 648ba84fe8f..73716d4edf3 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js
@@ -16,27 +16,27 @@ export const mockLabels = [
];
export const mockSuggestedColors = {
- '#0033CC': 'UA blue',
- '#428BCA': 'Moderate blue',
- '#44AD8E': 'Lime green',
- '#A8D695': 'Feijoa',
- '#5CB85C': 'Slightly desaturated green',
- '#69D100': 'Bright green',
- '#004E00': 'Very dark lime green',
- '#34495E': 'Very dark desaturated blue',
- '#7F8C8D': 'Dark grayish cyan',
- '#A295D6': 'Slightly desaturated blue',
- '#5843AD': 'Dark moderate blue',
- '#8E44AD': 'Dark moderate violet',
- '#FFECDB': 'Very pale orange',
- '#AD4363': 'Dark moderate pink',
- '#D10069': 'Strong pink',
- '#CC0033': 'Strong red',
- '#FF0000': 'Pure red',
- '#D9534F': 'Soft red',
- '#D1D100': 'Strong yellow',
- '#F0AD4E': 'Soft orange',
- '#AD8D43': 'Dark moderate orange',
+ '#009966': 'Green-cyan',
+ '#8fbc8f': 'Dark sea green',
+ '#3cb371': 'Medium sea green',
+ '#00b140': 'Green screen',
+ '#013220': 'Dark green',
+ '#6699cc': 'Blue-gray',
+ '#0000ff': 'Blue',
+ '#e6e6fa': 'Lavendar',
+ '#9400d3': 'Dark violet',
+ '#330066': 'Deep violet',
+ '#808080': 'Gray',
+ '#36454f': 'Charcoal grey',
+ '#f7e7ce': 'Champagne',
+ '#c21e56': 'Rose red',
+ '#cc338b': 'Magenta-pink',
+ '#dc143c': 'Crimson',
+ '#ff0000': 'Red',
+ '#cd5b45': 'Dark coral',
+ '#eee600': 'Titanium yellow',
+ '#ed9121': 'Carrot orange',
+ '#c39953': 'Aztec Gold',
};
export const mockConfig = {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
index 951f706421f..59b170bfba9 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
+import { GlIcon, GlButton } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
-import { GlIcon, GlButton } from '@gitlab/ui';
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
index 0f49fe4fc5b..c4a645082e6 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
+import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
-import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue';
import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
index 989cd256e26..60903933505 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
@@ -1,6 +1,3 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-
import {
GlIntersectionObserver,
GlButton,
@@ -8,14 +5,16 @@ import {
GlSearchBoxByType,
GlLink,
} from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
-import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state';
-import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations';
import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions';
import * as getters from '~/vue_shared/components/sidebar/labels_select_vue/store/getters';
+import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations';
+import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state';
import { mockConfig, mockLabels, mockRegularLabel } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js
index 97946993857..1175d183c6c 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
index c1d9be7393c..726a113dbd9 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
-import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js
index 70311f8235f..0d1d6ebcfe5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js
@@ -1,7 +1,7 @@
-import Vuex from 'vuex';
+import { GlLabel } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
-import { GlLabel } from '@gitlab/ui';
import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue';
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js
index a6ec01ad7e1..bd1705e7693 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js
@@ -1,6 +1,6 @@
+import { GlIcon, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlIcon, GlLink } from '@gitlab/ui';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
import { mockRegularLabel } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
index 1206450bbeb..4cf36df2502 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
@@ -1,15 +1,15 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
-import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
-import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
-import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue';
+import { isInViewport } from '~/lib/utils/common_utils';
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
+import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
+import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue';
+import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
-import { isInViewport } from '~/lib/utils/common_utils';
import { mockConfig } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js
index 9697d6c30f2..85a14226585 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js
@@ -50,25 +50,25 @@ export const mockConfig = {
};
export const mockSuggestedColors = {
- '#0033CC': 'UA blue',
- '#428BCA': 'Moderate blue',
- '#44AD8E': 'Lime green',
- '#A8D695': 'Feijoa',
- '#5CB85C': 'Slightly desaturated green',
- '#69D100': 'Bright green',
- '#004E00': 'Very dark lime green',
- '#34495E': 'Very dark desaturated blue',
- '#7F8C8D': 'Dark grayish cyan',
- '#A295D6': 'Slightly desaturated blue',
- '#5843AD': 'Dark moderate blue',
- '#8E44AD': 'Dark moderate violet',
- '#FFECDB': 'Very pale orange',
- '#AD4363': 'Dark moderate pink',
- '#D10069': 'Strong pink',
- '#CC0033': 'Strong red',
- '#FF0000': 'Pure red',
- '#D9534F': 'Soft red',
- '#D1D100': 'Strong yellow',
- '#F0AD4E': 'Soft orange',
- '#AD8D43': 'Dark moderate orange',
+ '#009966': 'Green-cyan',
+ '#8fbc8f': 'Dark sea green',
+ '#3cb371': 'Medium sea green',
+ '#00b140': 'Green screen',
+ '#013220': 'Dark green',
+ '#6699cc': 'Blue-gray',
+ '#0000ff': 'Blue',
+ '#e6e6fa': 'Lavendar',
+ '#9400d3': 'Dark violet',
+ '#330066': 'Deep violet',
+ '#808080': 'Gray',
+ '#36454f': 'Charcoal grey',
+ '#f7e7ce': 'Champagne',
+ '#c21e56': 'Rose red',
+ '#cc338b': 'Magenta-pink',
+ '#dc143c': 'Crimson',
+ '#ff0000': 'Red',
+ '#cd5b45': 'Dark coral',
+ '#eee600': 'Titanium yellow',
+ '#ed9121': 'Carrot orange',
+ '#c39953': 'Aztec Gold',
};
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js
index 4909c43bc96..3f11095cb04 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js
@@ -1,11 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state';
-import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types';
-import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions';
-
import axios from '~/lib/utils/axios_utils';
+import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions';
+import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types';
+import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state';
describe('LabelsSelect Actions', () => {
let state;
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
index 208f2f2d42d..ab266ac8aed 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations';
import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types';
+import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations';
describe('LabelsSelect Mutations', () => {
describe(`${types.SET_INITIAL_STATE}`, () => {
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 e5f9b94128e..8802a832781 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,5 @@
-import Vue from 'vue';
import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue';
describe('Toggle Button', () => {
diff --git a/spec/frontend/vue_shared/components/table_pagination_spec.js b/spec/frontend/vue_shared/components/table_pagination_spec.js
index 12c47637358..ed23a47c328 100644
--- a/spec/frontend/vue_shared/components/table_pagination_spec.js
+++ b/spec/frontend/vue_shared/components/table_pagination_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlPagination } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
describe('Pagination component', () => {
diff --git a/spec/frontend/vue_shared/components/tabs/tabs_spec.js b/spec/frontend/vue_shared/components/tabs/tabs_spec.js
index 49d92094b34..fe7be5be899 100644
--- a/spec/frontend/vue_shared/components/tabs/tabs_spec.js
+++ b/spec/frontend/vue_shared/components/tabs/tabs_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import Tabs from '~/vue_shared/components/tabs/tabs';
import Tab from '~/vue_shared/components/tabs/tab.vue';
+import Tabs from '~/vue_shared/components/tabs/tabs';
describe('Tabs component', () => {
let vm;
diff --git a/spec/frontend/vue_shared/components/todo_button_spec.js b/spec/frontend/vue_shared/components/todo_button_spec.js
index 1f8a214d632..8043bb7785b 100644
--- a/spec/frontend/vue_shared/components/todo_button_spec.js
+++ b/spec/frontend/vue_shared/components/todo_button_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import TodoButton from '~/vue_shared/components/todo_button.vue';
describe('Todo Button', () => {
@@ -33,7 +33,7 @@ describe('Todo Button', () => {
it.each`
label | isTodo
${'Mark as done'} | ${true}
- ${'Add a To Do'} | ${false}
+ ${'Add a to do'} | ${false}
`('sets correct label when isTodo is $isTodo', ({ label, isTodo }) => {
createComponent({ isTodo });
diff --git a/spec/frontend/vue_shared/components/toggle_button_spec.js b/spec/frontend/vue_shared/components/toggle_button_spec.js
deleted file mode 100644
index 2822b1999bc..00000000000
--- a/spec/frontend/vue_shared/components/toggle_button_spec.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlIcon } from '@gitlab/ui';
-import ToggleButton from '~/vue_shared/components/toggle_button.vue';
-
-describe('Toggle Button component', () => {
- let wrapper;
-
- function createComponent(propsData = {}) {
- wrapper = shallowMount(ToggleButton, {
- propsData,
- });
- }
-
- const findInput = () => wrapper.find('input');
- const findButton = () => wrapper.find('button');
- const findToggleIcon = () => wrapper.find(GlIcon);
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('renders input with provided name', () => {
- createComponent({
- name: 'foo',
- });
-
- expect(findInput().attributes('name')).toBe('foo');
- });
-
- describe.each`
- value | iconName
- ${true} | ${'status_success_borderless'}
- ${false} | ${'status_failed_borderless'}
- `('when `value` prop is `$value`', ({ value, iconName }) => {
- beforeEach(() => {
- createComponent({
- value,
- name: 'foo',
- });
- });
-
- it('renders input with correct value attribute', () => {
- expect(findInput().attributes('value')).toBe(`${value}`);
- });
-
- it('renders correct icon', () => {
- const icon = findToggleIcon();
- expect(icon.isVisible()).toBe(true);
- expect(icon.props('name')).toBe(iconName);
- expect(findButton().classes('is-checked')).toBe(value);
- });
-
- describe('when clicked', () => {
- it('emits `change` event with correct event', async () => {
- findButton().trigger('click');
- await wrapper.vm.$nextTick();
-
- expect(wrapper.emitted('change')).toStrictEqual([[!value]]);
- });
- });
- });
-
- describe('when `disabledInput` prop is `true`', () => {
- beforeEach(() => {
- createComponent({
- value: true,
- disabledInput: true,
- });
- });
-
- it('renders disabled button', () => {
- expect(findButton().classes()).toContain('is-disabled');
- });
-
- it('does not emit change event when clicked', async () => {
- findButton().trigger('click');
- await wrapper.vm.$nextTick();
-
- expect(wrapper.emitted('change')).toBeFalsy();
- });
- });
-
- describe('when `isLoading` prop is `true`', () => {
- beforeEach(() => {
- createComponent({
- value: true,
- isLoading: true,
- });
- });
-
- it('renders loading class', () => {
- expect(findButton().classes()).toContain('is-loading');
- });
- });
-});
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 11982eb513d..ace486b1f32 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
+++ b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
jest.mock('~/flash');
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 d151cd15bc4..d62c4a98b10 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
@@ -1,10 +1,10 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { each } from 'lodash';
import { trimText } from 'helpers/text_helper';
-import { shallowMount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
describe('User Avatar Link Component', () => {
let wrapper;
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 e3cd2bb9aaa..1d15da491cd 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
-import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
const TEST_IMAGE_SIZE = 7;
const TEST_BREAKPOINT = 5;
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 435c3a5406e..a6c5e23ae14 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
@@ -1,8 +1,8 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
-import UserAvailabilityStatus from '~/set_status_modal/components/user_availability_status.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
+import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
const DEFAULT_PROPS = {
user: {
@@ -36,7 +36,7 @@ describe('User Popover Component', () => {
const findByTestId = (testid) => wrapper.find(`[data-testid="${testid}"]`);
const findUserStatus = () => wrapper.find('.js-user-status');
const findTarget = () => document.querySelector('.js-user-link');
- const findAvailabilityStatus = () => wrapper.find(UserAvailabilityStatus);
+ const findUserName = () => wrapper.find(UserNameWithStatus);
const createWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(UserPopover, {
@@ -47,7 +47,7 @@ describe('User Popover Component', () => {
},
stubs: {
GlSprintf,
- UserAvailabilityStatus,
+ UserNameWithStatus,
},
...options,
});
@@ -213,7 +213,7 @@ describe('User Popover Component', () => {
createWrapper({ user });
- expect(findAvailabilityStatus().exists()).toBe(true);
+ expect(findUserName().exists()).toBe(true);
expect(wrapper.text()).toContain(user.name);
expect(wrapper.text()).toContain('(Busy)');
});
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 8ed072bed13..eb23a8ef457 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
+import ActionsButton from '~/vue_shared/components/actions_button.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
-import ActionsButton from '~/vue_shared/components/actions_button.vue';
const TEST_EDIT_URL = '/gitlab-test/test/-/edit/master/';
const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/master/-/';
diff --git a/spec/frontend/vue_shared/directives/tooltip_spec.js b/spec/frontend/vue_shared/directives/tooltip_spec.js
index 28ec23ad4c1..99e8b5b552b 100644
--- a/spec/frontend/vue_shared/directives/tooltip_spec.js
+++ b/spec/frontend/vue_shared/directives/tooltip_spec.js
@@ -1,6 +1,6 @@
+import { mount } from '@vue/test-utils';
import $ from 'jquery';
import { escape } from 'lodash';
-import { mount } from '@vue/test-utils';
import tooltip from '~/vue_shared/directives/tooltip';
const DEFAULT_TOOLTIP_TEMPLATE = '<div v-tooltip :title="tooltip"></div>';
diff --git a/spec/frontend/vue_shared/directives/track_event_spec.js b/spec/frontend/vue_shared/directives/track_event_spec.js
index 8d867c8e3fc..d7d7f4edc3f 100644
--- a/spec/frontend/vue_shared/directives/track_event_spec.js
+++ b/spec/frontend/vue_shared/directives/track_event_spec.js
@@ -1,11 +1,11 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Tracking from '~/tracking';
import TrackEvent from '~/vue_shared/directives/track_event';
jest.mock('~/tracking');
-const Component = Vue.component('dummy-element', {
+const Component = Vue.component('DummyElement', {
directives: {
TrackEvent,
},
diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js
index b3ff7daef2b..7918f70d702 100644
--- a/spec/frontend/vue_shared/security_reports/mock_data.js
+++ b/spec/frontend/vue_shared/security_reports/mock_data.js
@@ -322,6 +322,23 @@ export const secretScanningDiffSuccessMock = {
head_report_created_at: '2020-01-10T10:00:00.000Z',
};
+export const securityReportDownloadPathsQueryNoArtifactsResponse = {
+ project: {
+ mergeRequest: {
+ headPipeline: {
+ id: 'gid://gitlab/Ci::Pipeline/176',
+ jobs: {
+ nodes: [],
+ __typename: 'CiJobConnection',
+ },
+ __typename: 'Pipeline',
+ },
+ __typename: 'MergeRequest',
+ },
+ __typename: 'Project',
+ },
+};
+
export const securityReportDownloadPathsQueryResponse = {
project: {
mergeRequest: {
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 50d1d130675..0b4816a951e 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
@@ -1,6 +1,7 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { merge } from 'lodash';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -8,26 +9,26 @@ import { trimText } from 'helpers/text_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
expectedDownloadDropdownProps,
+ securityReportDownloadPathsQueryNoArtifactsResponse,
securityReportDownloadPathsQueryResponse,
sastDiffSuccessMock,
secretScanningDiffSuccessMock,
} from 'jest/vue_shared/security_reports/mock_data';
-import Api from '~/api';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
+import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
+import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
-import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
-import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
-import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
+import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
jest.mock('~/flash');
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(VueApollo);
+Vue.use(Vuex);
const SAST_COMPARISON_PATH = '/sast.json';
const SECRET_SCANNING_COMPARISON_PATH = '/secret_detection.json';
@@ -47,7 +48,6 @@ describe('Security reports app', () => {
SecurityReportsApp,
merge(
{
- localVue,
propsData: { ...props },
stubs: {
HelpIcon: true,
@@ -60,187 +60,94 @@ describe('Security reports app', () => {
const pendingHandler = () => new Promise(() => {});
const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse });
+ const successEmptyHandler = () =>
+ Promise.resolve({ data: securityReportDownloadPathsQueryNoArtifactsResponse });
const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
const createMockApolloProvider = (handler) => {
- localVue.use(VueApollo);
-
const requestHandlers = [[securityReportDownloadPathsQuery, handler]];
return createMockApollo(requestHandlers);
};
- const anyParams = expect.any(Object);
-
const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown);
- const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]');
const findHelpIconComponent = () => wrapper.find(HelpIcon);
- const setupMockJobArtifact = (reportType) => {
- jest
- .spyOn(Api, 'pipelineJobs')
- .mockResolvedValue({ data: [{ artifacts: [{ file_type: reportType }] }] });
- };
- const expectPipelinesTabAnchor = () => {
- const mrTabsMock = { tabShown: jest.fn() };
- window.mrTabs = mrTabsMock;
- findPipelinesTabAnchor().trigger('click');
- expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]);
- };
afterEach(() => {
wrapper.destroy();
- delete window.mrTabs;
});
- describe.each([false, true])(
- 'given the coreSecurityMrWidgetCounts feature flag is %p',
- (coreSecurityMrWidgetCounts) => {
- const createComponentWithFlag = (options) =>
- createComponent(
- merge(
- {
- provide: {
- glFeatures: {
- coreSecurityMrWidgetCounts,
- },
- },
- },
- options,
- ),
- );
-
- describe.each(SecurityReportsApp.reportTypes)('given a report type %p', (reportType) => {
- beforeEach(() => {
- window.mrTabs = { tabShown: jest.fn() };
- setupMockJobArtifact(reportType);
- createComponentWithFlag();
- return wrapper.vm.$nextTick();
- });
-
- it('calls the pipelineJobs API correctly', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
- expect(Api.pipelineJobs).toHaveBeenCalledWith(
- props.projectId,
- props.pipelineId,
- anyParams,
- );
- });
-
- it('renders the expected message', () => {
- expect(wrapper.text()).toMatchInterpolatedText(
- SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance,
- );
- });
-
- describe('clicking the anchor to the pipelines tab', () => {
- it('calls the mrTabs.tabShown global', () => {
- expectPipelinesTabAnchor();
- });
- });
-
- it('renders a help link', () => {
- expect(findHelpIconComponent().props()).toEqual({
- helpPath: props.securityReportsDocsPath,
- discoverProjectSecurityPath: props.discoverProjectSecurityPath,
- });
- });
+ describe('given the artifacts query is loading', () => {
+ beforeEach(() => {
+ createComponent({
+ apolloProvider: createMockApolloProvider(pendingHandler),
});
+ });
- describe('given a report type "foo"', () => {
- beforeEach(() => {
- setupMockJobArtifact('foo');
- createComponentWithFlag();
- return wrapper.vm.$nextTick();
- });
-
- it('calls the pipelineJobs API correctly', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
- expect(Api.pipelineJobs).toHaveBeenCalledWith(
- props.projectId,
- props.pipelineId,
- anyParams,
- );
- });
+ // TODO: Remove this assertion as part of
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
+ it('initially renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+ });
- it('renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
+ describe('given the artifacts query loads successfully', () => {
+ beforeEach(() => {
+ createComponent({
+ apolloProvider: createMockApolloProvider(successHandler),
});
+ });
- describe('security artifacts on last page of multi-page response', () => {
- const numPages = 3;
-
- beforeEach(() => {
- jest
- .spyOn(Api, 'pipelineJobs')
- .mockImplementation(async (projectId, pipelineId, { page }) => {
- const requestedPage = parseInt(page, 10);
- if (requestedPage < numPages) {
- return {
- // Some jobs with no relevant artifacts
- data: [{}, {}],
- headers: { 'x-next-page': String(requestedPage + 1) },
- };
- } else if (requestedPage === numPages) {
- return {
- data: [{ artifacts: [{ file_type: SecurityReportsApp.reportTypes[0] }] }],
- };
- }
-
- throw new Error('Test failed due to request of non-existent jobs page');
- });
-
- createComponentWithFlag();
- return wrapper.vm.$nextTick();
- });
+ it('renders the download dropdown', () => {
+ expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
+ });
- it('fetches all pages', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages);
- });
+ it('renders the expected message', () => {
+ expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun);
+ });
- it('renders the expected message', () => {
- expect(wrapper.text()).toMatchInterpolatedText(
- SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance,
- );
- });
+ it('renders a help link', () => {
+ expect(findHelpIconComponent().props()).toEqual({
+ helpPath: props.securityReportsDocsPath,
+ discoverProjectSecurityPath: props.discoverProjectSecurityPath,
});
+ });
+ });
- describe('given an error from the API', () => {
- let error;
-
- beforeEach(() => {
- error = new Error('an error');
- jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error);
- createComponentWithFlag();
- return wrapper.vm.$nextTick();
- });
+ describe('given the artifacts query loads successfully with no artifacts', () => {
+ beforeEach(() => {
+ createComponent({
+ apolloProvider: createMockApolloProvider(successEmptyHandler),
+ });
+ });
- it('calls the pipelineJobs API correctly', () => {
- expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
- expect(Api.pipelineJobs).toHaveBeenCalledWith(
- props.projectId,
- props.pipelineId,
- anyParams,
- );
- });
+ // TODO: Remove this assertion as part of
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
+ it('initially renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+ });
- it('renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
+ describe('given the artifacts query fails', () => {
+ beforeEach(() => {
+ createComponent({
+ apolloProvider: createMockApolloProvider(failureHandler),
+ });
+ });
- it('calls createFlash correctly', () => {
- expect(createFlash.mock.calls).toEqual([
- [
- {
- message: SecurityReportsApp.i18n.apiError,
- captureError: true,
- error,
- },
- ],
- ]);
- });
+ it('calls createFlash correctly', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: SecurityReportsApp.i18n.apiError,
+ captureError: true,
+ error: expect.any(Error),
});
- },
- );
+ });
+
+ // TODO: Remove this assertion as part of
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
+ it('renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+ });
describe('given the coreSecurityMrWidgetCounts feature flag is enabled', () => {
let mock;
@@ -253,6 +160,7 @@ describe('Security reports app', () => {
coreSecurityMrWidgetCounts: true,
},
},
+ apolloProvider: createMockApolloProvider(successHandler),
}),
);
@@ -274,11 +182,7 @@ describe('Security reports app', () => {
${REPORT_TYPE_SECRET_DETECTION} | ${'secretScanningComparisonPath'} | ${SECRET_SCANNING_COMPARISON_PATH} | ${secretScanningDiffSuccessMock} | ${SECRET_SCANNING_SUCCESS_MESSAGE}
`(
'given a $pathProp and $reportType artifact',
- ({ reportType, pathProp, path, successResponse, successMessage }) => {
- beforeEach(() => {
- setupMockJobArtifact(reportType);
- });
-
+ ({ pathProp, path, successResponse, successMessage }) => {
describe('when loading', () => {
beforeEach(() => {
mock = new MockAdapter(axios, { delayResponse: 1 });
@@ -294,11 +198,11 @@ describe('Security reports app', () => {
});
it('should have loading message', () => {
- expect(wrapper.text()).toBe('Security scanning is loading');
+ expect(wrapper.text()).toContain('Security scanning is loading');
});
- it('should not render the pipeline tab anchor', () => {
- expect(findPipelinesTabAnchor().exists()).toBe(false);
+ it('renders the download dropdown', () => {
+ expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
});
});
@@ -319,8 +223,8 @@ describe('Security reports app', () => {
expect(trimText(wrapper.text())).toContain(successMessage);
});
- it('should render the pipeline tab anchor', () => {
- expectPipelinesTabAnchor();
+ it('renders the download dropdown', () => {
+ expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
});
});
@@ -341,125 +245,25 @@ describe('Security reports app', () => {
expect(trimText(wrapper.text())).toContain('Loading resulted in an error');
});
- it('should render the pipeline tab anchor', () => {
- expectPipelinesTabAnchor();
+ it('renders the download dropdown', () => {
+ expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
});
});
- },
- );
- });
-
- describe('given coreSecurityMrWidgetDownloads feature flag is enabled', () => {
- const createComponentWithFlagEnabled = (options) =>
- createComponent(
- merge(options, {
- provide: {
- glFeatures: {
- coreSecurityMrWidgetDownloads: true,
- },
- },
- }),
- );
-
- describe('given the query is loading', () => {
- beforeEach(() => {
- createComponentWithFlagEnabled({
- apolloProvider: createMockApolloProvider(pendingHandler),
- });
- });
- // TODO: Remove this assertion as part of
- // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
- it('initially renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
- });
-
- describe('given the query loads successfully', () => {
- beforeEach(() => {
- createComponentWithFlagEnabled({
- apolloProvider: createMockApolloProvider(successHandler),
- });
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
- });
-
- it('renders the expected message', () => {
- const text = wrapper.text();
- expect(text).not.toContain(SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance);
- expect(text).toContain(SecurityReportsApp.i18n.scansHaveRun);
- });
+ describe('when the comparison endpoint is not provided', () => {
+ beforeEach(() => {
+ mock.onGet(path).replyOnce(500);
- it('should not render the pipeline tab anchor', () => {
- expect(findPipelinesTabAnchor().exists()).toBe(false);
- });
- });
+ createComponentWithFlagEnabled();
- describe('given the query fails', () => {
- beforeEach(() => {
- createComponentWithFlagEnabled({
- apolloProvider: createMockApolloProvider(failureHandler),
- });
- });
+ return waitForPromises();
+ });
- it('calls createFlash correctly', () => {
- expect(createFlash).toHaveBeenCalledWith({
- message: SecurityReportsApp.i18n.apiError,
- captureError: true,
- error: expect.any(Error),
+ it('renders the basic scansHaveRun message', () => {
+ expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun);
+ });
});
- });
-
- // TODO: Remove this assertion as part of
- // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
- it('renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
- });
- });
-
- describe('given coreSecurityMrWidgetCounts and coreSecurityMrWidgetDownloads feature flags are enabled', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(SAST_COMPARISON_PATH).replyOnce(200, sastDiffSuccessMock);
- mock.onGet(SECRET_SCANNING_COMPARISON_PATH).replyOnce(200, secretScanningDiffSuccessMock);
- createComponent({
- propsData: {
- sastComparisonPath: SAST_COMPARISON_PATH,
- secretScanningComparisonPath: SECRET_SCANNING_COMPARISON_PATH,
- },
- provide: {
- glFeatures: {
- coreSecurityMrWidgetCounts: true,
- coreSecurityMrWidgetDownloads: true,
- },
- },
- apolloProvider: createMockApolloProvider(successHandler),
- });
-
- return waitForPromises();
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
- });
-
- it('renders the expected counts message', () => {
- expect(trimText(wrapper.text())).toContain(
- 'Security scanning detected 3 potential vulnerabilities 2 Critical 1 High and 0 Others',
- );
- });
-
- it('should not render the pipeline tab anchor', () => {
- expect(findPipelinesTabAnchor().exists()).toBe(false);
- });
+ },
+ );
});
});
diff --git a/spec/frontend/vue_shared/security_reports/store/getters_spec.js b/spec/frontend/vue_shared/security_reports/store/getters_spec.js
index b146a281d7b..97746c7c38b 100644
--- a/spec/frontend/vue_shared/security_reports/store/getters_spec.js
+++ b/spec/frontend/vue_shared/security_reports/store/getters_spec.js
@@ -1,7 +1,3 @@
-import createState from '~/vue_shared/security_reports/store/state';
-import createSastState from '~/vue_shared/security_reports/store/modules/sast/state';
-import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
-import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
import {
groupedSummaryText,
allReportsHaveError,
@@ -11,6 +7,10 @@ import {
anyReportHasIssues,
summaryCounts,
} from '~/vue_shared/security_reports/store/getters';
+import createSastState from '~/vue_shared/security_reports/store/modules/sast/state';
+import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
+import createState from '~/vue_shared/security_reports/store/state';
+import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
import { CRITICAL, HIGH, LOW } from '~/vulnerabilities/constants';
const generateVuln = (severity) => ({ severity });
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js
index c9d1db8a504..6af07273cf6 100644
--- a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js
+++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import createState from '~/vue_shared/security_reports/store/modules/sast/state';
-import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
-import * as actions from '~/vue_shared/security_reports/store/modules/sast/actions';
import axios from '~/lib/utils/axios_utils';
+import * as actions from '~/vue_shared/security_reports/store/modules/sast/actions';
+import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
+import createState from '~/vue_shared/security_reports/store/modules/sast/state';
const diffEndpoint = 'diff-endpoint.json';
const blobPath = 'blob-path.json';
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
index fd611f38a34..d6119f44619 100644
--- a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
+++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
@@ -1,6 +1,6 @@
import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
-import createState from '~/vue_shared/security_reports/store/modules/sast/state';
import mutations from '~/vue_shared/security_reports/store/modules/sast/mutations';
+import createState from '~/vue_shared/security_reports/store/modules/sast/state';
const createIssue = ({ ...config }) => ({ changed: false, ...config });
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js
index beda1a55438..d22fee864e7 100644
--- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js
+++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
-import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
-import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions';
import axios from '~/lib/utils/axios_utils';
+import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions';
+import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
+import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
const diffEndpoint = 'diff-endpoint.json';
const blobPath = 'blob-path.json';
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js
index 13fcc0f47a3..42da7476a40 100644
--- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js
+++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js
@@ -1,6 +1,6 @@
import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
-import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
import mutations from '~/vue_shared/security_reports/store/modules/secret_detection/mutations';
+import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
const createIssue = ({ ...config }) => ({ changed: false, ...config });
diff --git a/spec/frontend/vue_shared/security_reports/utils_spec.js b/spec/frontend/vue_shared/security_reports/utils_spec.js
index 7e5a27694ef..aa9e54fa10c 100644
--- a/spec/frontend/vue_shared/security_reports/utils_spec.js
+++ b/spec/frontend/vue_shared/security_reports/utils_spec.js
@@ -1,9 +1,9 @@
-import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_SECRET_DETECTION,
REPORT_FILE_TYPES,
} from '~/vue_shared/security_reports/constants';
+import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils';
import {
securityReportDownloadPathsQueryResponse,
sastArtifacts,
diff --git a/spec/frontend/vuex_shared/modules/modal/actions_spec.js b/spec/frontend/vuex_shared/modules/modal/actions_spec.js
index a8269194c0b..c151049df2d 100644
--- a/spec/frontend/vuex_shared/modules/modal/actions_spec.js
+++ b/spec/frontend/vuex_shared/modules/modal/actions_spec.js
@@ -1,6 +1,6 @@
import testAction from 'helpers/vuex_action_helper';
-import * as types from '~/vuex_shared/modules/modal/mutation_types';
import * as actions from '~/vuex_shared/modules/modal/actions';
+import * as types from '~/vuex_shared/modules/modal/mutation_types';
describe('Vuex ModalModule actions', () => {
describe('open', () => {
diff --git a/spec/frontend/vuex_shared/modules/modal/mutations_spec.js b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
index eaaf196d1ec..8e9ab6e6c86 100644
--- a/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
+++ b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/vuex_shared/modules/modal/mutations';
import * as types from '~/vuex_shared/modules/modal/mutation_types';
+import mutations from '~/vuex_shared/modules/modal/mutations';
describe('Vuex ModalModule mutations', () => {
describe(`${types.SHOW}`, () => {
diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js
index 13e6bec47ab..ad062d04140 100644
--- a/spec/frontend/whats_new/components/app_spec.js
+++ b/spec/frontend/whats_new/components/app_spec.js
@@ -1,6 +1,6 @@
+import { GlDrawer, GlInfiniteScroll, GlTabs } from '@gitlab/ui';
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
-import { GlDrawer, GlInfiniteScroll, GlTabs } from '@gitlab/ui';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import App from '~/whats_new/components/app.vue';
diff --git a/spec/frontend/whats_new/store/actions_spec.js b/spec/frontend/whats_new/store/actions_spec.js
index 82f17a2726f..c4125d28aba 100644
--- a/spec/frontend/whats_new/store/actions_spec.js
+++ b/spec/frontend/whats_new/store/actions_spec.js
@@ -1,10 +1,10 @@
-import testAction from 'helpers/vuex_action_helper';
-import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import MockAdapter from 'axios-mock-adapter';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import testAction from 'helpers/vuex_action_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
import actions from '~/whats_new/store/actions';
import * as types from '~/whats_new/store/mutation_types';
-import axios from '~/lib/utils/axios_utils';
describe('whats new actions', () => {
describe('openDrawer', () => {
diff --git a/spec/frontend/whats_new/store/mutations_spec.js b/spec/frontend/whats_new/store/mutations_spec.js
index 4967fb51d2b..a95c6885a77 100644
--- a/spec/frontend/whats_new/store/mutations_spec.js
+++ b/spec/frontend/whats_new/store/mutations_spec.js
@@ -1,6 +1,6 @@
+import * as types from '~/whats_new/store/mutation_types';
import mutations from '~/whats_new/store/mutations';
import createState from '~/whats_new/store/state';
-import * as types from '~/whats_new/store/mutation_types';
describe('whats new mutations', () => {
let state;
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 d096a3cbdc6..b199f4f0c49 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
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils';
import { GlDrawer } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import { getDrawerBodyHeight } from '~/whats_new/utils/get_drawer_body_height';
describe('~/whats_new/utils/get_drawer_body_height', () => {
diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index e874d018e92..5cc1d2200d3 100644
--- a/spec/frontend/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -1,11 +1,11 @@
-import $ from 'jquery';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Dropzone from 'dropzone';
+import $ from 'jquery';
import Mousetrap from 'mousetrap';
+import initNotes from '~/init_notes';
import * as utils from '~/lib/utils/common_utils';
import ZenMode from '~/zen_mode';
-import initNotes from '~/init_notes';
describe('ZenMode', () => {
let mock;