summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__helpers__/datetime_helpers.js2
-rw-r--r--spec/frontend/__helpers__/dl_locator_helper.js13
-rw-r--r--spec/frontend/__helpers__/keep_alive_component_helper_spec.js6
-rw-r--r--spec/frontend/__helpers__/matchers/to_validate_json_schema_spec.js4
-rw-r--r--spec/frontend/__helpers__/shared_test_setup.js3
-rw-r--r--spec/frontend/__mocks__/sortablejs/index.js2
-rw-r--r--spec/frontend/access_tokens/components/access_token_table_app_spec.js15
-rw-r--r--spec/frontend/access_tokens/components/expires_at_field_spec.js16
-rw-r--r--spec/frontend/access_tokens/components/new_access_token_app_spec.js41
-rw-r--r--spec/frontend/access_tokens/index_spec.js2
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js12
-rw-r--r--spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js4
-rw-r--r--spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js4
-rw-r--r--spec/frontend/admin/topics/components/topic_select_spec.js91
-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.js8
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js6
-rw-r--r--spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js12
-rw-r--r--spec/frontend/alerts_settings/components/alerts_form_spec.js2
-rw-r--r--spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js10
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_form_spec.js6
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js18
-rw-r--r--spec/frontend/analytics/components/activity_chart_spec.js2
-rw-r--r--spec/frontend/analytics/shared/components/daterange_spec.js15
-rw-r--r--spec/frontend/analytics/shared/components/metric_popover_spec.js6
-rw-r--r--spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js6
-rw-r--r--spec/frontend/analytics/usage_trends/components/app_spec.js6
-rw-r--r--spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js12
-rw-r--r--spec/frontend/analytics/usage_trends/components/users_chart_spec.js8
-rw-r--r--spec/frontend/analytics/usage_trends/utils_spec.js6
-rw-r--r--spec/frontend/api/harbor_registry_spec.js107
-rw-r--r--spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js4
-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.js2
-rw-r--r--spec/frontend/autosave_spec.js44
-rw-r--r--spec/frontend/badges/components/badge_settings_spec.js6
-rw-r--r--spec/frontend/batch_comments/components/diff_file_drafts_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js8
-rw-r--r--spec/frontend/batch_comments/components/preview_dropdown_spec.js2
-rw-r--r--spec/frontend/batch_comments/components/preview_item_spec.js2
-rw-r--r--spec/frontend/batch_comments/components/publish_dropdown_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/review_bar_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/submit_dropdown_spec.js35
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js3
-rw-r--r--spec/frontend/behaviors/bind_in_out_spec.js6
-rw-r--r--spec/frontend/blob/sketch/index_spec.js22
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js20
-rw-r--r--spec/frontend/boards/board_list_helper.js1
-rw-r--r--spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap2
-rw-r--r--spec/frontend/boards/components/board_blocked_icon_spec.js74
-rw-r--r--spec/frontend/boards/components/board_card_move_to_position_spec.js133
-rw-r--r--spec/frontend/boards/components/board_card_spec.js9
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js2
-rw-r--r--spec/frontend/boards/components/issue_due_date_spec.js2
-rw-r--r--spec/frontend/boards/components/item_count_spec.js4
-rw-r--r--spec/frontend/boards/mock_data.js76
-rw-r--r--spec/frontend/boards/stores/actions_spec.js38
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js25
-rw-r--r--spec/frontend/branches/components/divergence_graph_spec.js4
-rw-r--r--spec/frontend/captcha/captcha_modal_spec.js2
-rw-r--r--spec/frontend/cascading_settings/components/lock_popovers_spec.js4
-rw-r--r--spec/frontend/chronic_duration_spec.js2
-rw-r--r--spec/frontend/ci_lint/components/ci_lint_spec.js6
-rw-r--r--spec/frontend/ci_secure_files/components/secure_files_list_spec.js4
-rw-r--r--spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js12
-rw-r--r--spec/frontend/ci_variable_list/components/ci_project_variables_spec.js215
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js30
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js2
-rw-r--r--spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js8
-rw-r--r--spec/frontend/ci_variable_list/mocks.js15
-rw-r--r--spec/frontend/ci_variable_list/store/mutations_spec.js2
-rw-r--r--spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js96
-rw-r--r--spec/frontend/clusters/agents/components/integration_status_spec.js111
-rw-r--r--spec/frontend/clusters/agents/components/show_spec.js6
-rw-r--r--spec/frontend/clusters_list/components/agent_table_spec.js6
-rw-r--r--spec/frontend/clusters_list/components/agents_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/clusters_main_view_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/install_agent_modal_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/node_error_help_text_spec.js2
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js4
-rw-r--r--spec/frontend/code_navigation/components/popover_spec.js8
-rw-r--r--spec/frontend/code_navigation/utils/index_spec.js4
-rw-r--r--spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js216
-rw-r--r--spec/frontend/commit/commit_pipeline_status_component_spec.js4
-rw-r--r--spec/frontend/commit/mock_data.js211
-rw-r--r--spec/frontend/commit/pipelines/pipelines_table_spec.js27
-rw-r--r--spec/frontend/confidential_merge_request/components/dropdown_spec.js6
-rw-r--r--spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap62
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js126
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/code_block_spec.js)8
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/formatting_spec.js)11
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/link_spec.js)136
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/media_spec.js)13
-rw-r--r--spec/frontend/content_editor/components/content_editor_alert_spec.js25
-rw-r--r--spec/frontend/content_editor/components/content_editor_spec.js213
-rw-r--r--spec/frontend/content_editor/components/editor_state_observer_spec.js26
-rw-r--r--spec/frontend/content_editor/components/loading_indicator_spec.js46
-rw-r--r--spec/frontend/content_editor/components/toolbar_image_button_spec.js21
-rw-r--r--spec/frontend/content_editor/components/toolbar_link_button_spec.js18
-rw-r--r--spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js17
-rw-r--r--spec/frontend/content_editor/components/toolbar_table_button_spec.js14
-rw-r--r--spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/code_block_spec.js6
-rw-r--r--spec/frontend/content_editor/extensions/paste_markdown_spec.js21
-rw-r--r--spec/frontend/content_editor/remark_markdown_processing_spec.js73
-rw-r--r--spec/frontend/content_editor/render_html_and_json_for_all_examples.js6
-rw-r--r--spec/frontend/content_editor/services/content_editor_spec.js95
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js21
-rw-r--r--spec/frontend/crm/form_spec.js5
-rw-r--r--spec/frontend/crm/mock_data.js22
-rw-r--r--spec/frontend/crm/organizations_root_spec.js92
-rw-r--r--spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap12
-rw-r--r--spec/frontend/cycle_analytics/base_spec.js2
-rw-r--r--spec/frontend/cycle_analytics/path_navigation_spec.js12
-rw-r--r--spec/frontend/cycle_analytics/value_stream_metrics_spec.js2
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js2
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js4
-rw-r--r--spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js6
-rw-r--r--spec/frontend/deprecated_jquery_dropdown_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_spec.js1
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js40
-rw-r--r--spec/frontend/design_management/components/design_presentation_spec.js2
-rw-r--r--spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap4
-rw-r--r--spec/frontend/design_management/components/toolbar/index_spec.js2
-rw-r--r--spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap8
-rw-r--r--spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap4
-rw-r--r--spec/frontend/design_management/pages/index_spec.js2
-rw-r--r--spec/frontend/diffs/components/app_spec.js36
-rw-r--r--spec/frontend/diffs/components/collapsed_files_warning_spec.js4
-rw-r--r--spec/frontend/diffs/components/commit_item_spec.js6
-rw-r--r--spec/frontend/diffs/components/commit_widget_spec.js2
-rw-r--r--spec/frontend/diffs/components/compare_dropdown_layout_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_code_quality_spec.js3
-rw-r--r--spec/frontend/diffs/components/diff_comment_cell_spec.js8
-rw-r--r--spec/frontend/diffs/components/diff_content_spec.js12
-rw-r--r--spec/frontend/diffs/components/diff_discussion_reply_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_discussions_spec.js16
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js23
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js8
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js12
-rw-r--r--spec/frontend/diffs/components/diff_gutter_avatars_spec.js12
-rw-r--r--spec/frontend/diffs/components/diff_line_note_form_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_line_spec.js65
-rw-r--r--spec/frontend/diffs/components/diff_stats_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_view_spec.js21
-rw-r--r--spec/frontend/diffs/components/image_diff_overlay_spec.js2
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js2
-rw-r--r--spec/frontend/diffs/components/tree_list_spec.js4
-rw-r--r--spec/frontend/editor/components/source_editor_toolbar_spec.js2
-rw-r--r--spec/frontend/editor/source_editor_extension_spec.js2
-rw-r--r--spec/frontend/editor/source_editor_instance_spec.js6
-rw-r--r--spec/frontend/editor/source_editor_webide_ext_spec.js6
-rw-r--r--spec/frontend/emoji/components/category_spec.js10
-rw-r--r--spec/frontend/emoji/components/utils_spec.js4
-rw-r--r--spec/frontend/emoji/index_spec.js98
-rw-r--r--spec/frontend/environments/deployment_spec.js83
-rw-r--r--spec/frontend/environments/environment_table_spec.js2
-rw-r--r--spec/frontend/environments/environments_app_spec.js1
-rw-r--r--spec/frontend/environments/graphql/mock_data.js38
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js2
-rw-r--r--spec/frontend/environments/new_environment_spec.js2
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js6
-rw-r--r--spec/frontend/error_tracking/components/stacktrace_entry_spec.js4
-rw-r--r--spec/frontend/error_tracking_settings/components/app_spec.js10
-rw-r--r--spec/frontend/error_tracking_settings/components/project_dropdown_spec.js12
-rw-r--r--spec/frontend/feature_flags/components/environments_dropdown_spec.js2
-rw-r--r--spec/frontend/feature_flags/store/edit/actions_spec.js8
-rw-r--r--spec/frontend/feature_flags/store/index/actions_spec.js8
-rw-r--r--spec/frontend/feature_flags/store/new/actions_spec.js4
-rw-r--r--spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js12
-rw-r--r--spec/frontend/filtered_search/droplab/drop_down_spec.js12
-rw-r--r--spec/frontend/fixtures/api_merge_requests.rb2
-rw-r--r--spec/frontend/fixtures/api_projects.rb2
-rw-r--r--spec/frontend/fixtures/application_settings.rb2
-rw-r--r--spec/frontend/fixtures/blob.rb2
-rw-r--r--spec/frontend/fixtures/branches.rb2
-rw-r--r--spec/frontend/fixtures/clusters.rb2
-rw-r--r--spec/frontend/fixtures/deploy_keys.rb8
-rw-r--r--spec/frontend/fixtures/groups.rb2
-rw-r--r--spec/frontend/fixtures/issues.rb2
-rw-r--r--spec/frontend/fixtures/jobs.rb2
-rw-r--r--spec/frontend/fixtures/labels.rb2
-rw-r--r--spec/frontend/fixtures/merge_requests.rb2
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb2
-rw-r--r--spec/frontend/fixtures/metrics_dashboard.rb2
-rw-r--r--spec/frontend/fixtures/pipeline_schedules.rb2
-rw-r--r--spec/frontend/fixtures/pipelines.rb2
-rw-r--r--spec/frontend/fixtures/projects.rb2
-rw-r--r--spec/frontend/fixtures/raw.rb2
-rw-r--r--spec/frontend/fixtures/search.rb69
-rw-r--r--spec/frontend/fixtures/snippet.rb2
-rw-r--r--spec/frontend/fixtures/startup_css.rb16
-rw-r--r--spec/frontend/fixtures/todos.rb2
-rw-r--r--spec/frontend/flash_spec.js2
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_item_spec.js14
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_spec.js10
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_search_input_spec.js2
-rw-r--r--spec/frontend/frequent_items/utils_spec.js8
-rw-r--r--spec/frontend/google_cloud/databases/panel_spec.js17
-rw-r--r--spec/frontend/google_tag_manager/index_spec.js45
-rw-r--r--spec/frontend/groups/components/app_spec.js14
-rw-r--r--spec/frontend/groups/components/empty_state_spec.js2
-rw-r--r--spec/frontend/groups/components/group_item_spec.js34
-rw-r--r--spec/frontend/groups/components/groups_spec.js4
-rw-r--r--spec/frontend/groups/components/invite_members_banner_spec.js14
-rw-r--r--spec/frontend/groups/components/item_caret_spec.js4
-rw-r--r--spec/frontend/groups/components/item_stats_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/components/overview_tabs_spec.js187
-rw-r--r--spec/frontend/groups/components/visibility_level_dropdown_spec.js70
-rw-r--r--spec/frontend/header_search/init_spec.js10
-rw-r--r--spec/frontend/header_search/mock_data.js44
-rw-r--r--spec/frontend/header_search/store/actions_spec.js66
-rw-r--r--spec/frontend/header_search/store/getters_spec.js24
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_spec.js2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/radio_group_spec.js8
-rw-r--r--spec/frontend/ide/components/preview/navigator_spec.js4
-rw-r--r--spec/frontend/ide/components/shared/tokened_input_spec.js2
-rw-r--r--spec/frontend/ide/init_gitlab_web_ide_spec.js62
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js2
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js2
-rw-r--r--spec/frontend/import_entities/components/group_dropdown_spec.js2
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_spec.js18
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js4
-rw-r--r--spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js10
-rw-r--r--spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js18
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js16
-rw-r--r--spec/frontend/import_entities/import_projects/store/getters_spec.js4
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js14
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js6
-rw-r--r--spec/frontend/integrations/edit/components/trigger_fields_spec.js4
-rw-r--r--spec/frontend/invite_members/components/import_project_members_modal_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js32
-rw-r--r--spec/frontend/invite_members/components/user_limit_notification_spec.js37
-rw-r--r--spec/frontend/issuable/components/issue_assignees_spec.js2
-rw-r--r--spec/frontend/issuable/components/issue_milestone_spec.js2
-rw-r--r--spec/frontend/issuable/issuable_form_spec.js231
-rw-r--r--spec/frontend/issuable/related_issues/components/issue_token_spec.js6
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_block_spec.js4
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_list_spec.js4
-rw-r--r--spec/frontend/issues/create_merge_request_dropdown_spec.js2
-rw-r--r--spec/frontend/issues/list/components/issue_card_time_info_spec.js10
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js29
-rw-r--r--spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js10
-rw-r--r--spec/frontend/issues/new/components/title_suggestions_item_spec.js6
-rw-r--r--spec/frontend/issues/new/components/title_suggestions_spec.js2
-rw-r--r--spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js4
-rw-r--r--spec/frontend/issues/show/components/app_spec.js2
-rw-r--r--spec/frontend/issues/show/components/description_spec.js56
-rw-r--r--spec/frontend/issues/show/components/edit_actions_spec.js82
-rw-r--r--spec/frontend/issues/show/components/fields/description_spec.js2
-rw-r--r--spec/frontend/issues/show/components/fields/title_spec.js2
-rw-r--r--spec/frontend/issues/show/components/header_actions_spec.js10
-rw-r--r--spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js17
-rw-r--r--spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js44
-rw-r--r--spec/frontend/issues/show/components/incidents/highlight_bar_spec.js2
-rw-r--r--spec/frontend/issues/show/components/incidents/incident_tabs_spec.js8
-rw-r--r--spec/frontend/issues/show/components/incidents/mock_data.js42
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js40
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js27
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js157
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js19
-rw-r--r--spec/frontend/issues/show/components/incidents/utils_spec.js6
-rw-r--r--spec/frontend/issues/show/components/pinned_links_spec.js2
-rw-r--r--spec/frontend/issues/show/components/sentry_error_stack_trace_spec.js8
-rw-r--r--spec/frontend/jira_connect/branches/components/new_branch_form_spec.js2
-rw-r--r--spec/frontend/jira_connect/subscriptions/api_spec.js118
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js2
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js21
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js87
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js2
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js33
-rw-r--r--spec/frontend/jira_connect/subscriptions/store/actions_spec.js16
-rw-r--r--spec/frontend/jira_import/components/jira_import_app_spec.js10
-rw-r--r--spec/frontend/jira_import/components/jira_import_form_spec.js15
-rw-r--r--spec/frontend/jira_import/components/jira_import_progress_spec.js2
-rw-r--r--spec/frontend/jira_import/components/jira_import_setup_spec.js2
-rw-r--r--spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js34
-rw-r--r--spec/frontend/jobs/components/filtered_search/utils_spec.js19
-rw-r--r--spec/frontend/jobs/components/job/artifacts_block_spec.js (renamed from spec/frontend/jobs/components/artifacts_block_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/commit_block_spec.js (renamed from spec/frontend/jobs/components/commit_block_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/empty_state_spec.js (renamed from spec/frontend/jobs/components/empty_state_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/environments_block_spec.js (renamed from spec/frontend/jobs/components/environments_block_spec.js)4
-rw-r--r--spec/frontend/jobs/components/job/erased_block_spec.js (renamed from spec/frontend/jobs/components/erased_block_spec.js)4
-rw-r--r--spec/frontend/jobs/components/job/job_app_spec.js (renamed from spec/frontend/jobs/components/job_app_spec.js)30
-rw-r--r--spec/frontend/jobs/components/job/job_container_item_spec.js (renamed from spec/frontend/jobs/components/job_container_item_spec.js)4
-rw-r--r--spec/frontend/jobs/components/job/job_log_controllers_spec.js (renamed from spec/frontend/jobs/components/job_log_controllers_spec.js)4
-rw-r--r--spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js (renamed from spec/frontend/jobs/components/job_retry_forward_deployment_modal_spec.js)8
-rw-r--r--spec/frontend/jobs/components/job/job_sidebar_details_container_spec.js (renamed from spec/frontend/jobs/components/job_sidebar_details_container_spec.js)8
-rw-r--r--spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js (renamed from spec/frontend/jobs/components/job_sidebar_retry_button_spec.js)4
-rw-r--r--spec/frontend/jobs/components/job/jobs_container_spec.js (renamed from spec/frontend/jobs/components/jobs_container_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js156
-rw-r--r--spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js91
-rw-r--r--spec/frontend/jobs/components/job/manual_variables_form_spec.js (renamed from spec/frontend/jobs/components/manual_variables_form_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/sidebar_detail_row_spec.js (renamed from spec/frontend/jobs/components/sidebar_detail_row_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/sidebar_header_spec.js91
-rw-r--r--spec/frontend/jobs/components/job/sidebar_spec.js (renamed from spec/frontend/jobs/components/sidebar_spec.js)81
-rw-r--r--spec/frontend/jobs/components/job/stages_dropdown_spec.js (renamed from spec/frontend/jobs/components/stages_dropdown_spec.js)4
-rw-r--r--spec/frontend/jobs/components/job/stuck_block_spec.js (renamed from spec/frontend/jobs/components/stuck_block_spec.js)6
-rw-r--r--spec/frontend/jobs/components/job/trigger_block_spec.js (renamed from spec/frontend/jobs/components/trigger_block_spec.js)2
-rw-r--r--spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js (renamed from spec/frontend/jobs/components/unmet_prerequisites_block_spec.js)6
-rw-r--r--spec/frontend/jobs/components/log/line_header_spec.js4
-rw-r--r--spec/frontend/jobs/components/log/line_spec.js2
-rw-r--r--spec/frontend/jobs/components/table/job_table_app_spec.js14
-rw-r--r--spec/frontend/jobs/store/actions_spec.js20
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js4
-rw-r--r--spec/frontend/labels/components/delete_label_modal_spec.js2
-rw-r--r--spec/frontend/lib/dompurify_spec.js2
-rw-r--r--spec/frontend/lib/gfm/index_spec.js376
-rw-r--r--spec/frontend/lib/utils/apollo_startup_js_link_spec.js2
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js2
-rw-r--r--spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js18
-rw-r--r--spec/frontend/lib/utils/finite_state_machine_spec.js4
-rw-r--r--spec/frontend/lib/utils/is_navigating_away_spec.js2
-rw-r--r--spec/frontend/lib/utils/navigation_utility_spec.js2
-rw-r--r--spec/frontend/lib/utils/poll_spec.js4
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js27
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js35
-rw-r--r--spec/frontend/lib/utils/vuex_module_mappers_spec.js2
-rw-r--r--spec/frontend/locale/sprintf_spec.js18
-rw-r--r--spec/frontend/members/components/avatars/user_avatar_spec.js2
-rw-r--r--spec/frontend/members/mock_data.js1
-rw-r--r--spec/frontend/members/store/actions_spec.js4
-rw-r--r--spec/frontend/members/utils_spec.js16
-rw-r--r--spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js4
-rw-r--r--spec/frontend/merge_conflicts/store/actions_spec.js14
-rw-r--r--spec/frontend/merge_request_tabs_spec.js2
-rw-r--r--spec/frontend/milestones/components/milestone_combobox_spec.js8
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap4
-rw-r--r--spec/frontend/monitoring/components/charts/stacked_column_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js10
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js3
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js2
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js2
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js2
-rw-r--r--spec/frontend/nav/components/top_nav_app_spec.js5
-rw-r--r--spec/frontend/nav/components/top_nav_dropdown_menu_spec.js2
-rw-r--r--spec/frontend/nav/components/top_nav_menu_item_spec.js2
-rw-r--r--spec/frontend/nav/components/top_nav_menu_sections_spec.js68
-rw-r--r--spec/frontend/nav/mock_data.js2
-rw-r--r--spec/frontend/notebook/cells/output/latex_spec.js2
-rw-r--r--spec/frontend/notebook/index_spec.js10
-rw-r--r--spec/frontend/notebook/lib/highlight_spec.js15
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js8
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js46
-rw-r--r--spec/frontend/notes/components/discussion_filter_spec.js81
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js8
-rw-r--r--spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js2
-rw-r--r--spec/frontend/notes/components/multiline_comment_form_spec.js2
-rw-r--r--spec/frontend/notes/components/note_actions/timeline_event_button_spec.js35
-rw-r--r--spec/frontend/notes/components/note_body_spec.js10
-rw-r--r--spec/frontend/notes/components/note_form_spec.js12
-rw-r--r--spec/frontend/notes/components/note_header_spec.js30
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js2
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js2
-rw-r--r--spec/frontend/notes/components/sort_discussion_spec.js102
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js31
-rw-r--r--spec/frontend/notes/stores/actions_spec.js101
-rw-r--r--spec/frontend/notes/stores/getters_spec.js6
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js2
-rw-r--r--spec/frontend/notifications/components/custom_notifications_modal_spec.js14
-rw-r--r--spec/frontend/notifications/components/notifications_dropdown_spec.js11
-rw-r--r--spec/frontend/operation_settings/components/metrics_settings_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js20
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js143
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js75
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js85
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js9
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js38
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js52
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js75
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js66
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/mock_data.js269
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js162
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js42
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js125
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/details_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap18
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js143
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js108
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js54
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js54
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js115
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js35
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js164
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js109
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js64
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js96
-rw-r--r--spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js (renamed from spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js)6
-rw-r--r--spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js2
-rw-r--r--spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js2
-rw-r--r--spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js4
-rw-r--r--spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js8
-rw-r--r--spec/frontend/pages/import/history/components/import_error_details_spec.js6
-rw-r--r--spec/frontend/pages/import/history/components/import_history_app_spec.js10
-rw-r--r--spec/frontend/pages/profiles/show/emoji_menu_spec.js115
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_form_spec.js173
-rw-r--r--spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js177
-rw-r--r--spec/frontend/pages/projects/graphs/code_coverage_spec.js8
-rw-r--r--spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js59
-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/settings_panel_spec.js223
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_content_spec.js2
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_form_spec.js50
-rw-r--r--spec/frontend/performance_bar/components/add_request_spec.js2
-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.js2
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_section_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/file-tree/container_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js109
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js2
-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_warnings_spec.js4
-rw-r--r--spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js3
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_home_spec.js2
-rw-r--r--spec/frontend/pipeline_new/components/legacy_pipeline_new_form_spec.js456
-rw-r--r--spec/frontend/pipeline_new/components/pipeline_new_form_spec.js12
-rw-r--r--spec/frontend/pipeline_new/components/refs_dropdown_spec.js4
-rw-r--r--spec/frontend/pipeline_wizard/components/commit_spec.js2
-rw-r--r--spec/frontend/pipeline_wizard/components/editor_spec.js11
-rw-r--r--spec/frontend/pipeline_wizard/components/input_wrapper_spec.js2
-rw-r--r--spec/frontend/pipeline_wizard/components/wrapper_spec.js125
-rw-r--r--spec/frontend/pipeline_wizard/mock/yaml.js2
-rw-r--r--spec/frontend/pipeline_wizard/pipeline_wizard_spec.js1
-rw-r--r--spec/frontend/pipelines/components/dag/dag_annotations_spec.js2
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js10
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js176
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mock_data.js407
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js149
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js (renamed from spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js)5
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js (renamed from spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js)10
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js20
-rw-r--r--spec/frontend/pipelines/graph/action_component_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/graph_component_spec.js8
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js24
-rw-r--r--spec/frontend/pipelines/graph/graph_view_selector_spec.js61
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js6
-rw-r--r--spec/frontend/pipelines/graph/job_name_component_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js16
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/mock_data.js242
-rw-r--r--spec/frontend/pipelines/graph/stage_column_component_spec.js10
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js2
-rw-r--r--spec/frontend/pipelines/header_component_spec.js6
-rw-r--r--spec/frontend/pipelines/performance_insights_modal_spec.js131
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_multi_actions_spec.js20
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js71
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js24
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js5
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js18
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js72
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js8
-rw-r--r--spec/frontend/pipelines/test_reports/test_summary_table_spec.js2
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js4
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js7
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_source_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js7
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js7
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js7
-rw-r--r--spec/frontend/pipelines/utils_spec.js44
-rw-r--r--spec/frontend/popovers/components/popovers_spec.js10
-rw-r--r--spec/frontend/profile/account/components/delete_account_modal_spec.js2
-rw-r--r--spec/frontend/profile/account/components/update_username_spec.js6
-rw-r--r--spec/frontend/profile/preferences/components/integration_view_spec.js2
-rw-r--r--spec/frontend/profile/preferences/components/profile_preferences_spec.js4
-rw-r--r--spec/frontend/projects/commit/components/form_modal_spec.js8
-rw-r--r--spec/frontend/projects/commit/store/mutations_spec.js2
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js10
-rw-r--r--spec/frontend/projects/compare/components/app_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/repo_dropdown_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/revision_card_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_spec.js6
-rw-r--r--spec/frontend/projects/components/project_delete_button_spec.js2
-rw-r--r--spec/frontend/projects/components/shared/delete_button_spec.js2
-rw-r--r--spec/frontend/projects/details/upload_button_spec.js6
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js17
-rw-r--r--spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js4
-rw-r--r--spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js10
-rw-r--r--spec/frontend/projects/settings/components/new_access_dropdown_spec.js8
-rw-r--r--spec/frontend/projects/settings/components/shared_runners_toggle_spec.js6
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/app_spec.js49
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js58
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/mock_data.js25
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js40
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js37
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js6
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js17
-rw-r--r--spec/frontend/related_issues/components/related_issuable_input_spec.js8
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js4
-rw-r--r--spec/frontend/releases/components/app_show_spec.js4
-rw-r--r--spec/frontend/releases/components/asset_links_form_spec.js14
-rw-r--r--spec/frontend/releases/components/evidence_block_spec.js14
-rw-r--r--spec/frontend/releases/components/issuable_stats_spec.js8
-rw-r--r--spec/frontend/releases/components/release_block_assets_spec.js2
-rw-r--r--spec/frontend/releases/components/release_block_footer_spec.js12
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js2
-rw-r--r--spec/frontend/releases/components/release_block_milestone_info_spec.js8
-rw-r--r--spec/frontend/releases/components/release_block_spec.js6
-rw-r--r--spec/frontend/releases/components/release_skeleton_loader_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.js6
-rw-r--r--spec/frontend/releases/components/tag_field_spec.js4
-rw-r--r--spec/frontend/releases/stores/modules/detail/actions_spec.js26
-rw-r--r--spec/frontend/releases/stores/modules/detail/getters_spec.js19
-rw-r--r--spec/frontend/releases/stores/modules/detail/mutations_spec.js7
-rw-r--r--spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js8
-rw-r--r--spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js2
-rw-r--r--spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js2
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js2
-rw-r--r--spec/frontend/reports/components/grouped_issues_list_spec.js8
-rw-r--r--spec/frontend/reports/components/report_item_spec.js4
-rw-r--r--spec/frontend/reports/grouped_test_report/components/modal_spec.js4
-rw-r--r--spec/frontend/reports/grouped_test_report/store/actions_spec.js4
-rw-r--r--spec/frontend/reports/mock_data/new_failures_with_null_files_report.json40
-rw-r--r--spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap3
-rw-r--r--spec/frontend/repository/components/blob_button_group_spec.js10
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js49
-rw-r--r--spec/frontend/repository/components/blob_viewers/csv_viewer_spec.js2
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js20
-rw-r--r--spec/frontend/repository/components/delete_blob_modal_spec.js2
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js9
-rw-r--r--spec/frontend/repository/components/new_directory_modal_spec.js2
-rw-r--r--spec/frontend/repository/components/preview/index_spec.js2
-rw-r--r--spec/frontend/repository/components/table/index_spec.js39
-rw-r--r--spec/frontend/repository/components/upload_blob_modal_spec.js2
-rw-r--r--spec/frontend/repository/log_tree_spec.js8
-rw-r--r--spec/frontend/repository/mock_data.js1
-rw-r--r--spec/frontend/repository/utils/commit_spec.js2
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js2
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js30
-rw-r--r--spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js164
-rw-r--r--spec/frontend/runner/components/cells/runner_status_cell_spec.js21
-rw-r--r--spec/frontend/runner/components/cells/runner_summary_cell_spec.js91
-rw-r--r--spec/frontend/runner/components/cells/runner_summary_field_spec.js49
-rw-r--r--spec/frontend/runner/components/runner_details_spec.js30
-rw-r--r--spec/frontend/runner/components/runner_header_spec.js6
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js75
-rw-r--r--spec/frontend/runner/components/runner_paused_badge_spec.js5
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js65
-rw-r--r--spec/frontend/runner/components/runner_stacked_layout_banner_spec.js39
-rw-r--r--spec/frontend/runner/components/runner_status_badge_spec.js20
-rw-r--r--spec/frontend/runner/components/runner_tag_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_tags_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_type_badge_spec.js17
-rw-r--r--spec/frontend/runner/components/runner_type_tabs_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js7
-rw-r--r--spec/frontend/runner/components/stat/runner_stats_spec.js33
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js55
-rw-r--r--spec/frontend/runner/runner_edit/runner_edit_app_spec.js (renamed from spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js)13
-rw-r--r--spec/frontend/runner/utils_spec.js13
-rw-r--r--spec/frontend/search/sidebar/components/radio_filter_spec.js2
-rw-r--r--spec/frontend/search/sort/components/app_spec.js2
-rw-r--r--spec/frontend/set_status_modal/set_status_form_spec.js167
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js45
-rw-r--r--spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js156
-rw-r--r--spec/frontend/set_status_modal/utils_spec.js3
-rw-r--r--spec/frontend/sidebar/assignee_title_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js2
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js2
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js12
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js8
-rw-r--r--spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js2
-rw-r--r--spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js25
-rw-r--r--spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js5
-rw-r--r--spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js12
-rw-r--r--spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js4
-rw-r--r--spec/frontend/sidebar/components/severity/sidebar_severity_spec.js2
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js18
-rw-r--r--spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js2
-rw-r--r--spec/frontend/sidebar/components/time_tracking/report_spec.js2
-rw-r--r--spec/frontend/sidebar/issuable_assignees_spec.js23
-rw-r--r--spec/frontend/sidebar/lock/issuable_lock_form_spec.js2
-rw-r--r--spec/frontend/sidebar/mock_data.js10
-rw-r--r--spec/frontend/sidebar/sidebar_mediator_spec.js2
-rw-r--r--spec/frontend/sidebar/sidebar_move_issue_spec.js11
-rw-r--r--spec/frontend/sidebar/todo_spec.js2
-rw-r--r--spec/frontend/snippets/components/edit_spec.js38
-rw-r--r--spec/frontend/snippets/components/show_spec.js22
-rw-r--r--spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js2
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_visibility_edit_spec.js20
-rw-r--r--spec/frontend/surveys/merge_request_performance/app_spec.js74
-rw-r--r--spec/frontend/terraform/components/states_table_spec.js2
-rw-r--r--spec/frontend/token_access/mock_data.js13
-rw-r--r--spec/frontend/token_access/token_access_spec.js28
-rw-r--r--spec/frontend/tooltips/components/tooltips_spec.js4
-rw-r--r--spec/frontend/user_lists/store/index/actions_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js13
-rw-r--r--spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js6
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js28
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js75
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js40
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap468
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap24
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js21
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js22
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js65
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js15
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js20
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js19
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js17
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js3
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/app_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js39
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js174
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js10
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js4
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_details_spec.js2
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_metrics_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap26
-rw-r--r--spec/frontend/vue_shared/components/ci_badge_link_spec.js22
-rw-r--r--spec/frontend/vue_shared/components/code_block_highlighted_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/code_block_spec.js82
-rw-r--r--spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/paginated_list_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/registry/registry_search_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap14
-rw-r--r--spec/frontend/vue_shared/components/user_callout_dismisser_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js41
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js6
-rw-r--r--spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js4
-rw-r--r--spec/frontend/work_items/components/item_title_spec.js2
-rw-r--r--spec/frontend/work_items/components/work_item_actions_spec.js48
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js79
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js14
-rw-r--r--spec/frontend/work_items/components/work_item_detail_modal_spec.js2
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js (renamed from spec/frontend/work_items/pages/work_item_detail_spec.js)120
-rw-r--r--spec/frontend/work_items/components/work_item_due_date_spec.js346
-rw-r--r--spec/frontend/work_items/components/work_item_information_spec.js9
-rw-r--r--spec/frontend/work_items/components/work_item_labels_spec.js6
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js122
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_spec.js98
-rw-r--r--spec/frontend/work_items/components/work_item_state_spec.js5
-rw-r--r--spec/frontend/work_items/components/work_item_title_spec.js6
-rw-r--r--spec/frontend/work_items/components/work_item_type_icon_spec.js39
-rw-r--r--spec/frontend/work_items/components/work_item_weight_spec.js214
-rw-r--r--spec/frontend/work_items/mock_data.js258
-rw-r--r--spec/frontend/work_items/pages/create_work_item_spec.js4
-rw-r--r--spec/frontend/work_items/router_spec.js39
-rw-r--r--spec/frontend/work_items_hierarchy/components/hierarchy_spec.js2
693 files changed, 12720 insertions, 5449 deletions
diff --git a/spec/frontend/__helpers__/datetime_helpers.js b/spec/frontend/__helpers__/datetime_helpers.js
index 25dbd1d477d..cbe627b7968 100644
--- a/spec/frontend/__helpers__/datetime_helpers.js
+++ b/spec/frontend/__helpers__/datetime_helpers.js
@@ -1,4 +1,4 @@
-import dateFormat from 'dateformat';
+import dateFormat from '~/lib/dateformat';
/**
* Returns a date object corresponding to the given date string.
diff --git a/spec/frontend/__helpers__/dl_locator_helper.js b/spec/frontend/__helpers__/dl_locator_helper.js
index b507dcd599d..591c034be9b 100644
--- a/spec/frontend/__helpers__/dl_locator_helper.js
+++ b/spec/frontend/__helpers__/dl_locator_helper.js
@@ -19,10 +19,13 @@ import { createWrapper, ErrorWrapper } from '@vue/test-utils';
* @returns Wrapper
*/
export const findDd = (dtLabel, wrapper) => {
- const dt = wrapper.findByText(dtLabel).element;
- const dd = dt.nextElementSibling;
- if (dt.tagName === 'DT' && dd.tagName === 'DD') {
- return createWrapper(dd, {});
+ const dtw = wrapper.findByText(dtLabel);
+ if (dtw.exists()) {
+ const dt = dtw.element;
+ const dd = dt.nextElementSibling;
+ if (dt.tagName === 'DT' && dd.tagName === 'DD') {
+ return createWrapper(dd, {});
+ }
}
- return ErrorWrapper(dtLabel);
+ return new ErrorWrapper(dtLabel);
};
diff --git a/spec/frontend/__helpers__/keep_alive_component_helper_spec.js b/spec/frontend/__helpers__/keep_alive_component_helper_spec.js
index dcccc14f396..54d397d0997 100644
--- a/spec/frontend/__helpers__/keep_alive_component_helper_spec.js
+++ b/spec/frontend/__helpers__/keep_alive_component_helper_spec.js
@@ -17,16 +17,16 @@ describe('keepAlive', () => {
});
it('converts a component to a keep-alive component', async () => {
- const { element } = wrapper.find(component);
+ const { element } = wrapper.findComponent(component);
await wrapper.vm.deactivate();
- expect(wrapper.find(component).exists()).toBe(false);
+ expect(wrapper.findComponent(component).exists()).toBe(false);
await wrapper.vm.activate();
// assert that when the component is destroyed and re-rendered, the
// newly rendered component has the reference to the old component
// (i.e. the old component was deactivated and activated)
- expect(wrapper.find(component).element).toBe(element);
+ expect(wrapper.findComponent(component).element).toBe(element);
});
});
diff --git a/spec/frontend/__helpers__/matchers/to_validate_json_schema_spec.js b/spec/frontend/__helpers__/matchers/to_validate_json_schema_spec.js
index fd42c710c65..e6096221528 100644
--- a/spec/frontend/__helpers__/matchers/to_validate_json_schema_spec.js
+++ b/spec/frontend/__helpers__/matchers/to_validate_json_schema_spec.js
@@ -38,7 +38,7 @@ describe('custom matcher toValidateJsonSchema', () => {
});
it('throws if not matching', () => {
- expect(() => expect(null).toValidateJsonSchema(schema)).toThrowError(
+ expect(() => expect(null).toValidateJsonSchema(schema)).toThrow(
`Expected the given data to pass the schema validation, but found that it was considered invalid. Errors:
Error with item : must be object`,
);
@@ -57,7 +57,7 @@ Error with item : must be object`,
});
it('throws if matching', () => {
- expect(() => expect({ fruit: 'apple' }).not.toValidateJsonSchema(schema)).toThrowError(
+ expect(() => expect({ fruit: 'apple' }).not.toValidateJsonSchema(schema)).toThrow(
'Expected the given data not to pass the schema validation, but found that it was considered valid.',
);
});
diff --git a/spec/frontend/__helpers__/shared_test_setup.js b/spec/frontend/__helpers__/shared_test_setup.js
index 4d6486544ca..45a7b8e0352 100644
--- a/spec/frontend/__helpers__/shared_test_setup.js
+++ b/spec/frontend/__helpers__/shared_test_setup.js
@@ -48,9 +48,6 @@ testUtilsConfig.deprecationWarningHandler = (method, message) => {
const ALLOWED_DEPRECATED_METHODS = [
// https://gitlab.com/gitlab-org/gitlab/-/issues/295679
'finding components with `find` or `get`',
-
- // https://gitlab.com/gitlab-org/gitlab/-/issues/295680
- 'finding components with `findAll`',
];
if (!ALLOWED_DEPRECATED_METHODS.includes(method)) {
global.console.error(message);
diff --git a/spec/frontend/__mocks__/sortablejs/index.js b/spec/frontend/__mocks__/sortablejs/index.js
index 5039af54542..d8bc8ae9bda 100644
--- a/spec/frontend/__mocks__/sortablejs/index.js
+++ b/spec/frontend/__mocks__/sortablejs/index.js
@@ -1,4 +1,4 @@
-const Sortablejs = jest.genMockFromModule('sortablejs');
+const Sortablejs = jest.createMockFromModule('sortablejs');
export default Sortablejs;
export const Sortable = Sortablejs;
diff --git a/spec/frontend/access_tokens/components/access_token_table_app_spec.js b/spec/frontend/access_tokens/components/access_token_table_app_spec.js
index 6013fa3ec39..aed3db4aa4c 100644
--- a/spec/frontend/access_tokens/components/access_token_table_app_spec.js
+++ b/spec/frontend/access_tokens/components/access_token_table_app_spec.js
@@ -190,6 +190,21 @@ describe('~/access_tokens/components/access_token_table_app', () => {
expect(button.props('category')).toBe('tertiary');
});
+ describe('revoke path', () => {
+ beforeEach(() => {
+ createComponent({ showRole: true });
+ });
+
+ it.each([{ revoke_path: null }, { revoke_path: undefined }])(
+ 'with %p, does not show revoke button',
+ async (input) => {
+ await triggerSuccess(defaultActiveAccessTokens.map((data) => ({ ...data, ...input })));
+
+ expect(findCells().at(6).findComponent(GlButton).exists()).toBe(false);
+ },
+ );
+ });
+
it('sorts rows alphabetically', async () => {
createComponent({ showRole: true });
await triggerSuccess();
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 646dc0d703f..491d2a0e323 100644
--- a/spec/frontend/access_tokens/components/expires_at_field_spec.js
+++ b/spec/frontend/access_tokens/components/expires_at_field_spec.js
@@ -58,4 +58,20 @@ describe('~/access_tokens/components/expires_at_field', () => {
expect(findDatepicker().props('defaultDate')).toStrictEqual(future);
});
+
+ it('should set the default expiration date to be 365 days', () => {
+ const offset = 365;
+ const today = new Date();
+ const future = getDateInFuture(today, offset);
+ createComponent({ defaultDateOffset: offset });
+
+ expect(findDatepicker().props('defaultDate')).toStrictEqual(future);
+ });
+
+ it('should set the default expiration date to maxDate, ignoring defaultDateOffset', () => {
+ const maxDate = new Date();
+ createComponent({ maxDate, defaultDateOffset: 2 });
+
+ expect(findDatepicker().props('defaultDate')).toStrictEqual(maxDate);
+ });
});
diff --git a/spec/frontend/access_tokens/components/new_access_token_app_spec.js b/spec/frontend/access_tokens/components/new_access_token_app_spec.js
index 9ccadbebf7a..d12d200d214 100644
--- a/spec/frontend/access_tokens/components/new_access_token_app_spec.js
+++ b/spec/frontend/access_tokens/components/new_access_token_app_spec.js
@@ -23,18 +23,27 @@ describe('~/access_tokens/components/new_access_token_app', () => {
};
const triggerSuccess = async (newToken = 'new token') => {
- wrapper.find(DomElementListener).vm.$emit(EVENT_SUCCESS, { detail: [{ new_token: newToken }] });
+ wrapper
+ .findComponent(DomElementListener)
+ .vm.$emit(EVENT_SUCCESS, { detail: [{ new_token: newToken }] });
await nextTick();
};
const triggerError = async (errors = ['1', '2']) => {
- wrapper.find(DomElementListener).vm.$emit(EVENT_ERROR, { detail: [{ errors }] });
+ wrapper.findComponent(DomElementListener).vm.$emit(EVENT_ERROR, { detail: [{ errors }] });
await nextTick();
};
beforeEach(() => {
// NewAccessTokenApp observes a form element
- setHTMLFixture(`<form id="${FORM_SELECTOR.slice(1)}"><input type="submit"/></form>`);
+ setHTMLFixture(
+ `<form id="${FORM_SELECTOR.slice(1)}">
+ <input type="text" id="expires_at" value="2022-01-01"/>
+ <input type="text" value='1'/>
+ <input type="checkbox" checked/>
+ <input type="submit" value="Create"/>
+ </form>`,
+ );
createComponent();
});
@@ -78,7 +87,6 @@ describe('~/access_tokens/components/new_access_token_app', () => {
.findByLabelText(sprintf(__('Your new %{accessTokenType}'), { accessTokenType }))
.attributes();
expect(inputAttributes).toMatchObject({
- class: expect.stringContaining('qa-created-access-token'),
'data-qa-selector': 'created_access_token_field',
});
});
@@ -94,12 +102,29 @@ describe('~/access_tokens/components/new_access_token_app', () => {
});
});
- it('should reset the form', async () => {
- const resetSpy = jest.spyOn(wrapper.vm.form, 'reset');
+ describe('when resetting the form', () => {
+ it('should reset selectively some input fields', async () => {
+ expect(document.querySelector('input[type=text]:not([id$=expires_at])').value).toBe('1');
+ expect(document.querySelector('input[type=checkbox]').checked).toBe(true);
+ await triggerSuccess();
- await triggerSuccess();
+ expect(document.querySelector('input[type=text]:not([id$=expires_at])').value).toBe('');
+ expect(document.querySelector('input[type=checkbox]').checked).toBe(false);
+ });
- expect(resetSpy).toHaveBeenCalled();
+ it('should not reset the date field', async () => {
+ expect(document.querySelector('input[type=text][id$=expires_at]').value).toBe('2022-01-01');
+ await triggerSuccess();
+
+ expect(document.querySelector('input[type=text][id$=expires_at]').value).toBe('2022-01-01');
+ });
+
+ it('should not reset the submit button value', async () => {
+ expect(document.querySelector('input[type=submit]').value).toBe('Create');
+ await triggerSuccess();
+
+ expect(document.querySelector('input[type=submit]').value).toBe('Create');
+ });
});
});
diff --git a/spec/frontend/access_tokens/index_spec.js b/spec/frontend/access_tokens/index_spec.js
index 0c611a4a512..55575ab25fc 100644
--- a/spec/frontend/access_tokens/index_spec.js
+++ b/spec/frontend/access_tokens/index_spec.js
@@ -182,7 +182,7 @@ describe('access tokens', () => {
});
describe('initTokensApp', () => {
- it('mounts the component and provides`tokenTypes` ', () => {
+ it('mounts the component and provides`tokenTypes`', () => {
const tokensData = {
[FEED_TOKEN]: FEED_TOKEN,
[INCOMING_EMAIL_TOKEN]: INCOMING_EMAIL_TOKEN,
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 bffadbde087..1d57473943b 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
@@ -48,8 +48,8 @@ describe('AddContextCommitsModal', () => {
return wrapper;
};
- const findModal = () => wrapper.find(GlModal);
- const findSearch = () => wrapper.find(GlSearchBoxByType);
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findSearch = () => wrapper.findComponent(GlSearchBoxByType);
beforeEach(() => {
wrapper = createWrapper();
@@ -75,7 +75,7 @@ describe('AddContextCommitsModal', () => {
it('when user starts entering text in search box, it calls action "searchCommits" after waiting for 500s', () => {
const searchText = 'abcd';
findSearch().vm.$emit('input', searchText);
- expect(searchCommits).not.toBeCalled();
+ expect(searchCommits).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(searchCommits).toHaveBeenCalledWith(expect.anything(), searchText);
});
@@ -107,12 +107,12 @@ describe('AddContextCommitsModal', () => {
it('a disabled ok button in first tab, when row is selected in second tab', () => {
createWrapper({ selectedContextCommits: [commit] });
- expect(wrapper.find(GlModal).attributes('ok-disabled')).toBe('true');
+ expect(wrapper.findComponent(GlModal).attributes('ok-disabled')).toBe('true');
});
});
describe('has an ok button when clicked calls action', () => {
- it('"createContextCommits" when only new commits to be added ', async () => {
+ it('"createContextCommits" when only new commits to be added', async () => {
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
findModal().vm.$emit('ok');
await nextTick();
@@ -121,7 +121,7 @@ describe('AddContextCommitsModal', () => {
forceReload: true,
});
});
- it('"removeContextCommits" when only added commits are to be removed ', async () => {
+ it('"removeContextCommits" when only added commits are to be removed', async () => {
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
findModal().vm.$emit('ok');
await nextTick();
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 85ecb4313c2..f679576182f 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
@@ -32,7 +32,7 @@ describe('ReviewTabContainer', () => {
it('shows loading icon when commits are being loaded', () => {
createWrapper({ isLoading: true });
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('shows loading error text when API call fails', () => {
@@ -46,6 +46,6 @@ describe('ReviewTabContainer', () => {
it('renders all passed commits as list', () => {
createWrapper({ commits: [commit] });
- expect(wrapper.findAll(CommitItem).length).toBe(1);
+ expect(wrapper.findAllComponents(CommitItem).length).toBe(1);
});
});
diff --git a/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js b/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js
index 534af2a3033..de56e843eb9 100644
--- a/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js
+++ b/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js
@@ -34,7 +34,7 @@ describe('DevopsScore', () => {
createComponent({ devopsScoreMetrics: {} });
});
- it('includes the DevopsScoreCallout component ', () => {
+ it('includes the DevopsScoreCallout component', () => {
expect(bannerExists()).toBe(true);
});
@@ -67,7 +67,7 @@ describe('DevopsScore', () => {
createComponent();
});
- it('includes the DevopsScoreCallout component ', () => {
+ it('includes the DevopsScoreCallout component', () => {
expect(bannerExists()).toBe(true);
});
diff --git a/spec/frontend/admin/topics/components/topic_select_spec.js b/spec/frontend/admin/topics/components/topic_select_spec.js
new file mode 100644
index 00000000000..f61af6203f0
--- /dev/null
+++ b/spec/frontend/admin/topics/components/topic_select_spec.js
@@ -0,0 +1,91 @@
+import { GlAvatarLabeled, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TopicSelect from '~/admin/topics/components/topic_select.vue';
+
+const mockTopics = [
+ { id: 1, name: 'topic1', title: 'Topic 1', avatarUrl: 'avatar.com/topic1.png' },
+ { id: 2, name: 'GitLab', title: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' },
+];
+
+describe('TopicSelect', () => {
+ let wrapper;
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+
+ function createComponent(props = {}) {
+ wrapper = shallowMount(TopicSelect, {
+ propsData: props,
+ data() {
+ return {
+ topics: mockTopics,
+ search: '',
+ };
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ topics: { loading: false },
+ },
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('mounts', () => {
+ createComponent();
+
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('`selectedTopic` prop defaults to `{}`', () => {
+ createComponent();
+
+ expect(wrapper.props('selectedTopic')).toEqual({});
+ });
+
+ it('`labelText` prop defaults to `null`', () => {
+ createComponent();
+
+ expect(wrapper.props('labelText')).toBe(null);
+ });
+
+ it('renders default text if no selected topic', () => {
+ createComponent();
+
+ expect(findDropdown().props('text')).toBe('Select a topic');
+ });
+
+ it('renders selected topic', () => {
+ createComponent({ selectedTopic: mockTopics[0] });
+
+ expect(findDropdown().props('text')).toBe('topic1');
+ });
+
+ it('renders label', () => {
+ createComponent({ labelText: 'my label' });
+
+ expect(wrapper.find('label').text()).toBe('my label');
+ });
+
+ it('renders dropdown items', () => {
+ createComponent();
+
+ const dropdownItems = findAllDropdownItems();
+
+ expect(dropdownItems.at(0).findComponent(GlAvatarLabeled).props('label')).toBe('Topic 1');
+ expect(dropdownItems.at(1).findComponent(GlAvatarLabeled).props('label')).toBe('GitLab');
+ });
+
+ it('emits `click` event when topic selected', () => {
+ createComponent();
+
+ findAllDropdownItems().at(0).vm.$emit('click');
+
+ expect(wrapper.emitted('click')).toEqual([[mockTopics[0]]]);
+ });
+});
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 c2bf90e7635..0d6bc1b74fb 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
@@ -25,7 +25,7 @@ describe('AlertManagementEmptyState', () => {
}
});
- const EmptyState = () => wrapper.find(GlEmptyState);
+ const EmptyState = () => wrapper.findComponent(GlEmptyState);
describe('Empty state', () => {
it('shows empty state', () => {
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 bba5fcbbf08..3a5fb99fdf1 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
@@ -28,8 +28,8 @@ describe('AlertManagementList', () => {
describe('Alert List Wrapper', () => {
it('should show the empty state when alerts are not enabled', () => {
- expect(wrapper.find(AlertManagementEmptyState).exists()).toBe(true);
- expect(wrapper.find(AlertManagementTable).exists()).toBe(false);
+ expect(wrapper.findComponent(AlertManagementEmptyState).exists()).toBe(true);
+ expect(wrapper.findComponent(AlertManagementTable).exists()).toBe(false);
});
it('should show the alerts table when alerts are enabled', () => {
@@ -39,8 +39,8 @@ describe('AlertManagementList', () => {
},
});
- expect(wrapper.find(AlertManagementEmptyState).exists()).toBe(false);
- expect(wrapper.find(AlertManagementTable).exists()).toBe(true);
+ expect(wrapper.findComponent(AlertManagementEmptyState).exists()).toBe(false);
+ expect(wrapper.findComponent(AlertManagementTable).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js
index 5b823694b99..3e1438c37d6 100644
--- a/spec/frontend/alert_management/components/alert_management_table_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -172,8 +172,8 @@ describe('AlertManagementTable', () => {
await nextTick();
- expect(wrapper.find(GlTable).exists()).toBe(true);
- expect(findAlertsTable().find(GlIcon).classes('icon-critical')).toBe(true);
+ expect(wrapper.findComponent(GlTable).exists()).toBe(true);
+ expect(findAlertsTable().findComponent(GlIcon).classes('icon-critical')).toBe(true);
});
it('renders severity text', () => {
@@ -200,7 +200,7 @@ describe('AlertManagementTable', () => {
loading: false,
});
- const avatar = findAssignees().at(1).find(GlAvatar);
+ const avatar = findAssignees().at(1).findComponent(GlAvatar);
const { src, label } = avatar.attributes();
const { name, avatarUrl } = mockAlerts[1].assignees.nodes[0];
diff --git a/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js b/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js
index dba9c8be669..1e125bdfd3a 100644
--- a/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js
+++ b/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js
@@ -47,7 +47,7 @@ describe('AlertMappingBuilder', () => {
expect(findColumnInRow(0, 2).text()).toContain(i18n.columns.payloadKeyTitle);
expect(findColumnInRow(0, 3).text()).toContain(i18n.columns.fallbackKeyTitle);
- const fallbackColumnIcon = findColumnInRow(0, 3).find(GlIcon);
+ const fallbackColumnIcon = findColumnInRow(0, 3).findComponent(GlIcon);
expect(fallbackColumnIcon.exists()).toBe(true);
expect(fallbackColumnIcon.attributes('name')).toBe('question');
expect(fallbackColumnIcon.attributes('title')).toBe(i18n.fallbackTooltip);
@@ -55,7 +55,7 @@ describe('AlertMappingBuilder', () => {
it('renders disabled form input for each mapped field', () => {
alertFields.forEach((field, index) => {
- const input = findColumnInRow(index + 1, 0).find(GlFormInput);
+ const input = findColumnInRow(index + 1, 0).findComponent(GlFormInput);
const types = field.types.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(' or ');
expect(input.attributes('value')).toBe(`${field.label} (${types})`);
expect(input.attributes('disabled')).toBe('');
@@ -71,7 +71,7 @@ describe('AlertMappingBuilder', () => {
it('renders mapping dropdown for each field', () => {
alertFields.forEach(({ types }, index) => {
- const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown);
+ const dropdown = findColumnInRow(index + 1, 2).findComponent(GlDropdown);
const { searchBox, dropdownItems, mappingOptions } = getDropdownContent(dropdown, types);
expect(dropdown.exists()).toBe(true);
@@ -82,7 +82,7 @@ describe('AlertMappingBuilder', () => {
it('renders fallback dropdown only for the fields that have fallback', () => {
alertFields.forEach(({ types, numberOfFallbacks }, index) => {
- const dropdown = findColumnInRow(index + 1, 3).find(GlDropdown);
+ const dropdown = findColumnInRow(index + 1, 3).findComponent(GlDropdown);
expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks));
if (numberOfFallbacks) {
@@ -96,8 +96,8 @@ describe('AlertMappingBuilder', () => {
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);
+ const dropdown = findColumnInRow(1, 2).findComponent(GlDropdown);
+ const option = dropdown.findComponent(GlDropdownItem);
option.vm.$emit('click');
expect(wrapper.emitted('onMappingUpdate')[0]).toEqual([mappingToSave]);
});
diff --git a/spec/frontend/alerts_settings/components/alerts_form_spec.js b/spec/frontend/alerts_settings/components/alerts_form_spec.js
index a045954dfb8..33098282bf8 100644
--- a/spec/frontend/alerts_settings/components/alerts_form_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_form_spec.js
@@ -5,7 +5,7 @@ describe('Alert integration settings form', () => {
let wrapper;
const service = { updateSettings: jest.fn().mockResolvedValue() };
- const findForm = () => wrapper.find({ ref: 'settingsForm' });
+ const findForm = () => wrapper.findComponent({ ref: 'settingsForm' });
beforeEach(() => {
wrapper = shallowMount(AlertsSettingsForm, {
diff --git a/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js b/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js
index 3ffbb7ab60a..9983af873c2 100644
--- a/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js
@@ -53,8 +53,8 @@ describe('AlertIntegrationsList', () => {
mountComponent();
});
- const findTableComponent = () => wrapper.find(GlTable);
- const findTableComponentRows = () => wrapper.find(GlTable).findAll('table tbody tr');
+ const findTableComponent = () => wrapper.findComponent(GlTable);
+ const findTableComponentRows = () => wrapper.findComponent(GlTable).findAll('table tbody tr');
const finsStatusCell = () => wrapper.findAll('[data-testid="integration-activated-status"]');
it('renders a table', () => {
@@ -67,7 +67,7 @@ describe('AlertIntegrationsList', () => {
});
it('renders an an edit and delete button for each integration', () => {
- expect(findTableComponent().findAll(GlButton).length).toBe(4);
+ expect(findTableComponent().findAllComponents(GlButton).length).toBe(4);
});
it('renders an highlighted row when a current integration is selected to edit', () => {
@@ -78,7 +78,7 @@ describe('AlertIntegrationsList', () => {
describe('integration status', () => {
it('enabled', () => {
const cell = finsStatusCell().at(0);
- const activatedIcon = cell.find(GlIcon);
+ const activatedIcon = cell.findComponent(GlIcon);
expect(cell.text()).toBe(i18n.status.enabled.name);
expect(activatedIcon.attributes('name')).toBe('check');
expect(activatedIcon.attributes('title')).toBe(i18n.status.enabled.tooltip);
@@ -86,7 +86,7 @@ describe('AlertIntegrationsList', () => {
it('disabled', () => {
const cell = finsStatusCell().at(1);
- const notActivatedIcon = cell.find(GlIcon);
+ const notActivatedIcon = cell.findComponent(GlIcon);
expect(cell.text()).toBe(i18n.status.disabled.name);
expect(notActivatedIcon.attributes('name')).toBe('warning-solid');
expect(notActivatedIcon.attributes('title')).toBe(i18n.status.disabled.tooltip);
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
index 7d9d2875cf8..fb9e97e7505 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
@@ -325,9 +325,9 @@ describe('AlertsSettingsForm', () => {
});
await nextTick();
- expect(findSamplePayloadSection().find(GlFormTextarea).attributes('disabled')).toBe(
- disabled,
- );
+ expect(
+ findSamplePayloadSection().findComponent(GlFormTextarea).attributes('disabled'),
+ ).toBe(disabled);
});
});
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
index ed185c11732..0266adeb6c7 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
@@ -63,14 +63,14 @@ describe('AlertsSettingsWrapper', () => {
const findLoader = () => wrapper.findComponent(IntegrationsList).findComponent(GlLoadingIcon);
const findIntegrationsList = () => wrapper.findComponent(IntegrationsList);
- const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
+ const findIntegrations = () => wrapper.findComponent(IntegrationsList).findAll('table tbody tr');
const findAddIntegrationBtn = () => wrapper.findByTestId('add-integration-btn');
const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm);
const findAlert = () => wrapper.findComponent(GlAlert);
function destroyHttpIntegration(localWrapper) {
localWrapper
- .find(IntegrationsList)
+ .findComponent(IntegrationsList)
.vm.$emit('delete-integration', { id: integrationToDestroy.id });
}
@@ -148,7 +148,7 @@ describe('AlertsSettingsWrapper', () => {
expect(findIntegrations()).toHaveLength(mockIntegrations.length);
});
- it('renders `Add new integration` button when multiple integrations are supported ', () => {
+ it('renders `Add new integration` button when multiple integrations are supported', () => {
createComponent({
data: {
integrations: mockIntegrations,
@@ -189,7 +189,7 @@ describe('AlertsSettingsWrapper', () => {
data: { integrations: [] },
loading: true,
});
- expect(wrapper.find(IntegrationsList).exists()).toBe(true);
+ expect(wrapper.findComponent(IntegrationsList).exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
});
@@ -321,7 +321,7 @@ describe('AlertsSettingsWrapper', () => {
});
});
- it('shows an error alert when integration creation fails ', async () => {
+ it('shows an error alert when integration creation fails', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(ADD_INTEGRATION_ERROR);
findAlertsSettingsForm().vm.$emit('create-new-integration', {});
@@ -330,7 +330,7 @@ describe('AlertsSettingsWrapper', () => {
expect(createFlash).toHaveBeenCalledWith({ message: ADD_INTEGRATION_ERROR });
});
- it('shows an error alert when integration token reset fails ', async () => {
+ it('shows an error alert when integration token reset fails', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(RESET_INTEGRATION_TOKEN_ERROR);
findAlertsSettingsForm().vm.$emit('reset-token', {});
@@ -339,7 +339,7 @@ describe('AlertsSettingsWrapper', () => {
expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
});
- it('shows an error alert when integration update fails ', async () => {
+ it('shows an error alert when integration update fails', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
findAlertsSettingsForm().vm.$emit('update-integration', {});
@@ -357,14 +357,14 @@ describe('AlertsSettingsWrapper', () => {
mock.restore();
});
- it('shows an error alert when integration test payload is invalid ', async () => {
+ it('shows an error alert when integration test payload is invalid', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
expect(createFlash).toHaveBeenCalledTimes(1);
});
- it('shows an error alert when integration is not activated ', async () => {
+ it('shows an error alert when integration is not activated', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.FORBIDDEN);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({
diff --git a/spec/frontend/analytics/components/activity_chart_spec.js b/spec/frontend/analytics/components/activity_chart_spec.js
index a6b45ffe20f..c26407f5c1d 100644
--- a/spec/frontend/analytics/components/activity_chart_spec.js
+++ b/spec/frontend/analytics/components/activity_chart_spec.js
@@ -18,7 +18,7 @@ describe('Activity Chart Bundle', () => {
wrapper = null;
});
- const findChart = () => wrapper.find(GlColumnChart);
+ const findChart = () => wrapper.findComponent(GlColumnChart);
const findNoData = () => wrapper.find('[data-testid="noActivityChartData"]');
describe('Activity Chart', () => {
diff --git a/spec/frontend/analytics/shared/components/daterange_spec.js b/spec/frontend/analytics/shared/components/daterange_spec.js
index a38df274243..7a09fe3319d 100644
--- a/spec/frontend/analytics/shared/components/daterange_spec.js
+++ b/spec/frontend/analytics/shared/components/daterange_spec.js
@@ -1,5 +1,5 @@
-import { GlDaterangePicker, GlSprintf } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
+import { GlDaterangePicker } from '@gitlab/ui';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import { useFakeDate } from 'helpers/fake_date';
import Daterange from '~/analytics/shared/components/daterange.vue';
@@ -13,13 +13,12 @@ describe('Daterange component', () => {
let wrapper;
- const factory = (props = defaultProps, mountFn = shallowMount) => {
+ const factory = (props = defaultProps, mountFn = shallowMountExtended) => {
wrapper = mountFn(Daterange, {
propsData: {
...defaultProps,
...props,
},
- stubs: { GlSprintf },
});
};
@@ -28,7 +27,7 @@ describe('Daterange component', () => {
});
const findDaterangePicker = () => wrapper.findComponent(GlDaterangePicker);
- const findDateRangeIndicator = () => wrapper.findComponent(GlSprintf);
+ const findDateRangeIndicator = () => wrapper.findByTestId('daterange-picker-indicator');
describe('template', () => {
describe('when show is false', () => {
@@ -52,7 +51,7 @@ describe('Daterange component', () => {
const endDate = new Date('2019-09-30');
const minDate = new Date('2019-06-01');
- factory({ show: true, startDate, endDate, minDate }, mount);
+ factory({ show: true, startDate, endDate, minDate }, mountExtended);
const input = findDaterangePicker().find('input');
input.setValue('2019-01-01');
@@ -64,7 +63,7 @@ describe('Daterange component', () => {
describe('with a maxDateRange being set', () => {
beforeEach(() => {
- factory({ maxDateRange: 30 });
+ factory({ maxDateRange: 30 }, mountExtended);
});
it('displays the max date range indicator', () => {
@@ -72,7 +71,7 @@ describe('Daterange component', () => {
});
it('displays the correct number of selected days in the indicator', () => {
- expect(findDateRangeIndicator().text()).toMatchInterpolatedText('10 days selected');
+ expect(findDateRangeIndicator().text()).toBe('10 days selected');
});
it('sets the tooltip', () => {
diff --git a/spec/frontend/analytics/shared/components/metric_popover_spec.js b/spec/frontend/analytics/shared/components/metric_popover_spec.js
index ffec77c2708..6a58f8c6d29 100644
--- a/spec/frontend/analytics/shared/components/metric_popover_spec.js
+++ b/spec/frontend/analytics/shared/components/metric_popover_spec.js
@@ -30,7 +30,7 @@ describe('MetricPopover', () => {
const findAllMetricLinks = () => wrapper.findAll('[data-testid="metric-link"]');
const findMetricDescription = () => wrapper.findByTestId('metric-description');
const findMetricDocsLink = () => wrapper.findByTestId('metric-docs-link');
- const findMetricDocsLinkIcon = () => findMetricDocsLink().find(GlIcon);
+ const findMetricDocsLinkIcon = () => findMetricDocsLink().findComponent(GlIcon);
afterEach(() => {
wrapper.destroy();
@@ -83,7 +83,9 @@ describe('MetricPopover', () => {
const allLinkContainers = findAllMetricLinks();
expect(allLinkContainers.at(idx).text()).toContain(link.name);
- expect(allLinkContainers.at(idx).find(GlLink).attributes('href')).toBe(link.url);
+ expect(allLinkContainers.at(idx).findComponent(GlLink).attributes('href')).toBe(
+ link.url,
+ );
});
});
diff --git a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
index 69918c1db65..3871fd530d8 100644
--- a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
+++ b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
@@ -79,11 +79,11 @@ describe('ProjectsDropdownFilter component', () => {
const findClearAllButton = () => wrapper.findByText('Clear all');
const findSelectedProjectsLabel = () => wrapper.findComponent(GlTruncate);
- const findDropdown = () => wrapper.find(GlDropdown);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () =>
findDropdown()
- .findAll(GlDropdownItem)
+ .findAllComponents(GlDropdownItem)
.filter((w) => w.text() !== 'No matching results');
const findDropdownAtIndex = (index) => findDropdownItems().at(index);
@@ -106,7 +106,7 @@ describe('ProjectsDropdownFilter component', () => {
};
// NOTE: Selected items are now visually separated from unselected items
- const findSelectedDropdownItems = () => findHighlightedItems().findAll(GlDropdownItem);
+ const findSelectedDropdownItems = () => findHighlightedItems().findAllComponents(GlDropdownItem);
const findSelectedDropdownAtIndex = (index) => findSelectedDropdownItems().at(index);
const findSelectedButtonIdentIconAtIndex = (index) =>
diff --git a/spec/frontend/analytics/usage_trends/components/app_spec.js b/spec/frontend/analytics/usage_trends/components/app_spec.js
index 156be26f895..c732dc22322 100644
--- a/spec/frontend/analytics/usage_trends/components/app_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/app_spec.js
@@ -21,13 +21,13 @@ describe('UsageTrendsApp', () => {
});
it('displays the usage counts component', () => {
- expect(wrapper.find(UsageCounts).exists()).toBe(true);
+ expect(wrapper.findComponent(UsageCounts).exists()).toBe(true);
});
['Total projects & groups', 'Pipelines', 'Issues & merge requests'].forEach((usage) => {
it(`displays the ${usage} chart`, () => {
const chartTitles = wrapper
- .findAll(UsageTrendsCountChart)
+ .findAllComponents(UsageTrendsCountChart)
.wrappers.map((chartComponent) => chartComponent.props('chartTitle'));
expect(chartTitles).toContain(usage);
@@ -35,6 +35,6 @@ describe('UsageTrendsApp', () => {
});
it('displays the users chart component', () => {
- expect(wrapper.find(UsersChart).exists()).toBe(true);
+ expect(wrapper.findComponent(UsersChart).exists()).toBe(true);
});
});
diff --git a/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js b/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js
index 02cf7f42a0b..ad6089f74b5 100644
--- a/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js
@@ -50,9 +50,9 @@ describe('UsageTrendsCountChart', () => {
wrapper = null;
});
- const findLoader = () => wrapper.find(ChartSkeletonLoader);
- const findChart = () => wrapper.find(GlLineChart);
- const findAlert = () => wrapper.find(GlAlert);
+ const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
+ const findChart = () => wrapper.findComponent(GlLineChart);
+ const findAlert = () => wrapper.findComponent(GlAlert);
describe('while loading', () => {
beforeEach(() => {
@@ -61,7 +61,7 @@ describe('UsageTrendsCountChart', () => {
});
it('requests data', () => {
- expect(queryHandler).toBeCalledTimes(1);
+ expect(queryHandler).toHaveBeenCalledTimes(1);
});
it('displays the skeleton loader', () => {
@@ -105,7 +105,7 @@ describe('UsageTrendsCountChart', () => {
});
it('requests data', () => {
- expect(queryHandler).toBeCalledTimes(1);
+ expect(queryHandler).toHaveBeenCalledTimes(1);
});
it('hides the skeleton loader', () => {
@@ -141,7 +141,7 @@ describe('UsageTrendsCountChart', () => {
});
it('requests data twice', () => {
- expect(queryHandler).toBeCalledTimes(2);
+ expect(queryHandler).toHaveBeenCalledTimes(2);
});
it('passes the data to the line chart', () => {
diff --git a/spec/frontend/analytics/usage_trends/components/users_chart_spec.js b/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
index 32a664a5026..e7abd4d4323 100644
--- a/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
@@ -47,9 +47,9 @@ describe('UsersChart', () => {
wrapper = null;
});
- const findLoader = () => wrapper.find(ChartSkeletonLoader);
- const findAlert = () => wrapper.find(GlAlert);
- const findChart = () => wrapper.find(GlAreaChart);
+ const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findChart = () => wrapper.findComponent(GlAreaChart);
describe('while loading', () => {
beforeEach(() => {
@@ -139,7 +139,7 @@ describe('UsersChart', () => {
});
it('requests data twice', () => {
- expect(queryHandler).toBeCalledTimes(2);
+ expect(queryHandler).toHaveBeenCalledTimes(2);
});
it('calls fetchMore', () => {
diff --git a/spec/frontend/analytics/usage_trends/utils_spec.js b/spec/frontend/analytics/usage_trends/utils_spec.js
index 656f310dda7..9982e96735e 100644
--- a/spec/frontend/analytics/usage_trends/utils_spec.js
+++ b/spec/frontend/analytics/usage_trends/utils_spec.js
@@ -16,17 +16,17 @@ describe('getAverageByMonth', () => {
expect(getAverageByMonth(mockCountsData2)).toStrictEqual(countsMonthlyChartData2);
});
- it('it transforms a data point to the first of the month', () => {
+ it('transforms a data point to the first of the month', () => {
const item = mockCountsData1[0];
const firstOfTheMonth = item.recordedAt.replace(/-[0-9]{2}$/, '-01');
expect(getAverageByMonth([item])).toStrictEqual([[firstOfTheMonth, item.count]]);
});
- it('it uses sane defaults', () => {
+ it('uses sane defaults', () => {
expect(getAverageByMonth()).toStrictEqual([]);
});
- it('it errors when passing null', () => {
+ it('errors when passing null', () => {
expect(() => {
getAverageByMonth(null);
}).toThrow();
diff --git a/spec/frontend/api/harbor_registry_spec.js b/spec/frontend/api/harbor_registry_spec.js
new file mode 100644
index 00000000000..8a4c377ebd1
--- /dev/null
+++ b/spec/frontend/api/harbor_registry_spec.js
@@ -0,0 +1,107 @@
+import MockAdapter from 'axios-mock-adapter';
+import * as harborRegistryApi from '~/api/harbor_registry';
+import axios from '~/lib/utils/axios_utils';
+import httpStatus from '~/lib/utils/http_status';
+
+describe('~/api/harbor_registry', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ jest.spyOn(axios, 'get');
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('getHarborRepositoriesList', () => {
+ it('fetches the harbor repositories of the configured harbor project', () => {
+ const requestPath = '/flightjs/Flight/-/harbor/repositories';
+ const expectedUrl = `${requestPath}.json`;
+ const expectedParams = {
+ limit: 10,
+ page: 1,
+ sort: 'update_time desc',
+ requestPath,
+ };
+ const expectResponse = [
+ {
+ harbor_id: 1,
+ name: 'test-project/image-1',
+ artifact_count: 1,
+ creation_time: '2022-07-16T08:20:34.851Z',
+ update_time: '2022-07-16T08:20:34.851Z',
+ harbor_project_id: 2,
+ pull_count: 0,
+ location: 'http://demo.harbor.com/harbor/projects/2/repositories/image-1',
+ },
+ ];
+ mock.onGet(expectedUrl).reply(httpStatus.OK, expectResponse);
+
+ return harborRegistryApi.getHarborRepositoriesList(expectedParams).then(({ data }) => {
+ expect(data).toEqual(expectResponse);
+ });
+ });
+ });
+
+ describe('getHarborArtifacts', () => {
+ it('fetches the artifacts of a particular harbor repository', () => {
+ const requestPath = '/flightjs/Flight/-/harbor/repositories';
+ const repoName = 'image-1';
+ const expectedUrl = `${requestPath}/${repoName}/artifacts.json`;
+ const expectedParams = {
+ limit: 10,
+ page: 1,
+ sort: 'name asc',
+ repoName,
+ requestPath,
+ };
+ const expectResponse = [
+ {
+ harbor_id: 1,
+ digest: 'sha256:dcdf379c574e1773d703f0c0d56d67594e7a91d6b84d11ff46799f60fb081c52',
+ size: 775241,
+ push_time: '2022-07-16T08:20:34.867Z',
+ tags: ['v2', 'v1', 'latest'],
+ },
+ ];
+ mock.onGet(expectedUrl).reply(httpStatus.OK, expectResponse);
+
+ return harborRegistryApi.getHarborArtifacts(expectedParams).then(({ data }) => {
+ expect(data).toEqual(expectResponse);
+ });
+ });
+ });
+
+ describe('getHarborTags', () => {
+ it('fetches the tags of a particular artifact', () => {
+ const requestPath = '/flightjs/Flight/-/harbor/repositories';
+ const repoName = 'image-1';
+ const digest = 'sha256:5d98daa36cdc8d6c7ed6579ce17230f0f9fd893a9012fc069cb7d714c0e3df35';
+ const expectedUrl = `${requestPath}/${repoName}/artifacts/${digest}/tags.json`;
+ const expectedParams = {
+ requestPath,
+ digest,
+ repoName,
+ };
+ const expectResponse = [
+ {
+ repositoryId: 4,
+ artifactId: 5,
+ id: 4,
+ name: 'latest',
+ pullTime: '0001-01-01T00:00:00.000Z',
+ pushTime: '2022-05-27T18:21:27.903Z',
+ signed: false,
+ immutable: false,
+ },
+ ];
+ mock.onGet(expectedUrl).reply(httpStatus.OK, expectResponse);
+
+ return harborRegistryApi.getHarborTags(expectedParams).then(({ data }) => {
+ expect(data).toEqual(expectResponse);
+ });
+ });
+ });
+});
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 2f3ff2b22f2..ca94acfa444 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
@@ -39,8 +39,8 @@ describe('Keep latest artifact checkbox', () => {
const fullPath = 'gitlab-org/gitlab';
const helpPagePath = '/help/ci/pipelines/job_artifacts';
- const findCheckbox = () => wrapper.find(GlFormCheckbox);
- const findHelpLink = () => wrapper.find(GlLink);
+ const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
+ const findHelpLink = () => wrapper.findComponent(GlLink);
const createComponent = (handlers) => {
requestHandlers = {
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 2dcc537809f..0d9196b88ed 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
@@ -31,11 +31,13 @@ describe('RecoveryCodes', () => {
};
const queryByText = (text, options) => within(wrapper.element).queryByText(text, options);
- const findAlert = () => wrapper.find(GlAlert);
+ const findAlert = () => wrapper.findComponent(GlAlert);
const findRecoveryCodes = () => wrapper.findByTestId('recovery-codes');
- const findCopyButton = () => wrapper.find(ClipboardButton);
+ const findCopyButton = () => wrapper.findComponent(ClipboardButton);
const findButtonByText = (text) =>
- wrapper.findAll(GlButton).wrappers.find((buttonWrapper) => buttonWrapper.text() === text);
+ wrapper
+ .findAllComponents(GlButton)
+ .wrappers.find((buttonWrapper) => buttonWrapper.text() === text);
const findDownloadButton = () => findButtonByText('Download codes');
const findPrintButton = () => findButtonByText('Print codes');
const findProceedButton = () => findButtonByText('Proceed');
diff --git a/spec/frontend/authentication/two_factor_auth/index_spec.js b/spec/frontend/authentication/two_factor_auth/index_spec.js
index f9a6b2df662..427159bacd9 100644
--- a/spec/frontend/authentication/two_factor_auth/index_spec.js
+++ b/spec/frontend/authentication/two_factor_auth/index_spec.js
@@ -10,7 +10,7 @@ describe('initRecoveryCodes', () => {
let el;
let wrapper;
- const findRecoveryCodesComponent = () => wrapper.find(RecoveryCodes);
+ const findRecoveryCodesComponent = () => wrapper.findComponent(RecoveryCodes);
beforeEach(() => {
el = document.createElement('div');
diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js
index c881e0f9794..7a9262cd004 100644
--- a/spec/frontend/autosave_spec.js
+++ b/spec/frontend/autosave_spec.js
@@ -8,6 +8,7 @@ describe('Autosave', () => {
let autosave;
const field = $('<textarea></textarea>');
+ const checkbox = $('<input type="checkbox">');
const key = 'key';
const fallbackKey = 'fallbackKey';
const lockVersionKey = 'lockVersionKey';
@@ -90,6 +91,24 @@ describe('Autosave', () => {
expect(eventHandler).toHaveBeenCalledTimes(1);
fieldElement.removeEventListener('change', eventHandler);
});
+
+ describe('if field type is checkbox', () => {
+ beforeEach(() => {
+ autosave = {
+ field: checkbox,
+ key,
+ isLocalStorageAvailable: true,
+ type: 'checkbox',
+ };
+ });
+
+ it('should restore', () => {
+ window.localStorage.setItem(key, true);
+ expect(checkbox.is(':checked')).toBe(false);
+ Autosave.prototype.restore.call(autosave);
+ expect(checkbox.is(':checked')).toBe(true);
+ });
+ });
});
describe('if field gets deleted from DOM', () => {
@@ -169,6 +188,31 @@ describe('Autosave', () => {
expect(window.localStorage.setItem).toHaveBeenCalled();
});
});
+
+ describe('if field type is checkbox', () => {
+ beforeEach(() => {
+ autosave = {
+ field: checkbox,
+ key,
+ isLocalStorageAvailable: true,
+ type: 'checkbox',
+ };
+ });
+
+ it('should save true when checkbox on', () => {
+ checkbox.prop('checked', true);
+ Autosave.prototype.save.call(autosave);
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(key, true);
+ });
+
+ it('should call reset when checkbox off', () => {
+ autosave.reset = jest.fn();
+ checkbox.prop('checked', false);
+ Autosave.prototype.save.call(autosave);
+ expect(autosave.reset).toHaveBeenCalled();
+ expect(window.localStorage.setItem).not.toHaveBeenCalled();
+ });
+ });
});
describe('save with lockVersion', () => {
diff --git a/spec/frontend/badges/components/badge_settings_spec.js b/spec/frontend/badges/components/badge_settings_spec.js
index 79cf5f3e4ff..bddb6d3801c 100644
--- a/spec/frontend/badges/components/badge_settings_spec.js
+++ b/spec/frontend/badges/components/badge_settings_spec.js
@@ -42,7 +42,7 @@ describe('BadgeSettings component', () => {
button.vm.$emit('click');
await nextTick();
- const modal = wrapper.find(GlModal);
+ const modal = wrapper.findComponent(GlModal);
expect(modal.isVisible()).toBe(true);
});
@@ -51,7 +51,7 @@ describe('BadgeSettings component', () => {
});
it('displays badge list', () => {
- expect(wrapper.find(BadgeList).isVisible()).toBe(true);
+ expect(wrapper.findComponent(BadgeList).isVisible()).toBe(true);
});
describe('when editing', () => {
@@ -64,7 +64,7 @@ describe('BadgeSettings component', () => {
});
it('displays no badge list', () => {
- expect(wrapper.find(BadgeList).isVisible()).toBe(false);
+ expect(wrapper.findComponent(BadgeList).isVisible()).toBe(false);
});
});
});
diff --git a/spec/frontend/batch_comments/components/diff_file_drafts_spec.js b/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
index 6a5ff1af7c9..c922d6a9809 100644
--- a/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
+++ b/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
@@ -35,13 +35,13 @@ describe('Batch comments diff file drafts component', () => {
it('renders list of draft notes', () => {
factory();
- expect(vm.findAll(DraftNote).length).toEqual(2);
+ expect(vm.findAllComponents(DraftNote).length).toEqual(2);
});
it('renders index of draft note', () => {
factory();
- const elements = vm.findAll(DesignNotePin);
+ const elements = vm.findAllComponents(DesignNotePin);
expect(elements.length).toEqual(2);
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index ccca4a2c3e9..03ecbc01a56 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -61,7 +61,7 @@ describe('Batch comments draft note component', () => {
createComponent();
expect(wrapper.findComponent(GlBadge).exists()).toBe(true);
- const note = wrapper.find(NoteableNote);
+ const note = wrapper.findComponent(NoteableNote);
expect(note.exists()).toBe(true);
expect(note.props().note).toEqual(draft);
@@ -115,14 +115,14 @@ describe('Batch comments draft note component', () => {
await nextTick();
const publishNowButton = findSubmitReviewButton();
- expect(publishNowButton.attributes().disabled).toBeTruthy();
+ expect(publishNowButton.attributes().disabled).toBe('true');
});
});
describe('update', () => {
it('dispatches updateDraft', async () => {
createComponent();
- const note = wrapper.find(NoteableNote);
+ const note = wrapper.findComponent(NoteableNote);
note.vm.$emit('handleEdit');
@@ -147,7 +147,7 @@ describe('Batch comments draft note component', () => {
createComponent();
jest.spyOn(window, 'confirm').mockImplementation(() => true);
- const note = wrapper.find(NoteableNote);
+ const note = wrapper.findComponent(NoteableNote);
note.vm.$emit('handleDeleteNote', draft);
diff --git a/spec/frontend/batch_comments/components/preview_dropdown_spec.js b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
index 079b64225e4..283632cb560 100644
--- a/spec/frontend/batch_comments/components/preview_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
@@ -53,7 +53,7 @@ describe('Batch comments preview dropdown', () => {
});
describe('clicking draft', () => {
- it('it toggles active file when viewDiffsFileByFile is true', async () => {
+ it('toggles active file when viewDiffsFileByFile is true', async () => {
factory({
viewDiffsFileByFile: true,
sortedDrafts: [{ id: 1, file_hash: 'hash' }],
diff --git a/spec/frontend/batch_comments/components/preview_item_spec.js b/spec/frontend/batch_comments/components/preview_item_spec.js
index cb71edd1238..91e6b84a216 100644
--- a/spec/frontend/batch_comments/components/preview_item_spec.js
+++ b/spec/frontend/batch_comments/components/preview_item_spec.js
@@ -118,7 +118,7 @@ describe('Batch comments draft preview item component', () => {
);
});
- it('it renders thread resolved text', () => {
+ it('renders thread resolved text', () => {
expect(vm.$el.querySelector('.draft-note-resolution').textContent).toContain(
'Thread will be resolved',
);
diff --git a/spec/frontend/batch_comments/components/publish_dropdown_spec.js b/spec/frontend/batch_comments/components/publish_dropdown_spec.js
index a3168931f1f..d1b7160d231 100644
--- a/spec/frontend/batch_comments/components/publish_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/publish_dropdown_spec.js
@@ -28,12 +28,12 @@ describe('Batch comments publish dropdown component', () => {
it('renders list of drafts', () => {
createComponent();
- expect(wrapper.findAll(GlDropdownItem).length).toBe(2);
+ expect(wrapper.findAllComponents(GlDropdownItem).length).toBe(2);
});
it('renders draft count in dropdown title', () => {
createComponent();
- expect(wrapper.find(GlDropdown).props('headerText')).toEqual('2 pending comments');
+ expect(wrapper.findComponent(GlDropdown).props('headerText')).toEqual('2 pending comments');
});
});
diff --git a/spec/frontend/batch_comments/components/review_bar_spec.js b/spec/frontend/batch_comments/components/review_bar_spec.js
index f50db6ab210..0a4c9ff62e4 100644
--- a/spec/frontend/batch_comments/components/review_bar_spec.js
+++ b/spec/frontend/batch_comments/components/review_bar_spec.js
@@ -24,7 +24,7 @@ describe('Batch comments review bar component', () => {
wrapper.destroy();
});
- it('it adds review-bar-visible class to body when review bar is mounted', async () => {
+ it('adds review-bar-visible class to body when review bar is mounted', async () => {
expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(false);
createComponent();
@@ -32,7 +32,7 @@ describe('Batch comments review bar component', () => {
expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(true);
});
- it('it removes review-bar-visible class to body when review bar is destroyed', async () => {
+ it('removes review-bar-visible class to body when review bar is destroyed', async () => {
createComponent();
wrapper.destroy();
diff --git a/spec/frontend/batch_comments/components/submit_dropdown_spec.js b/spec/frontend/batch_comments/components/submit_dropdown_spec.js
index 4f5ff797230..462ef7e7280 100644
--- a/spec/frontend/batch_comments/components/submit_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/submit_dropdown_spec.js
@@ -8,7 +8,7 @@ Vue.use(Vuex);
let wrapper;
let publishReview;
-function factory() {
+function factory({ canApprove = true } = {}) {
publishReview = jest.fn();
const store = new Vuex.Store({
@@ -17,8 +17,13 @@ function factory() {
markdownDocsPath: '/markdown/docs',
quickActionsDocsPath: '/quickactions/docs',
}),
- getNoteableData: () => ({ id: 1, preview_note_path: '/preview' }),
+ getNoteableData: () => ({
+ id: 1,
+ preview_note_path: '/preview',
+ current_user: { can_approve: canApprove },
+ }),
noteableType: () => 'merge_request',
+ getCurrentUserLastNote: () => ({ id: 1 }),
},
modules: {
batchComments: {
@@ -41,6 +46,7 @@ const findForm = () => wrapper.findByTestId('submit-gl-form');
describe('Batch comments submit dropdown', () => {
afterEach(() => {
wrapper.destroy();
+ window.mrTabs = null;
});
it('calls publishReview with note data', async () => {
@@ -54,9 +60,24 @@ describe('Batch comments submit dropdown', () => {
noteable_type: 'merge_request',
noteable_id: 1,
note: 'Hello world',
+ approve: false,
+ approval_password: '',
});
});
+ it('switches to the overview tab after submit', async () => {
+ window.mrTabs = { tabShown: jest.fn() };
+
+ factory();
+
+ findCommentTextarea().setValue('Hello world');
+
+ await findForm().vm.$emit('submit', { preventDefault: jest.fn() });
+ await Vue.nextTick();
+
+ expect(window.mrTabs.tabShown).toHaveBeenCalledWith('show');
+ });
+
it('sets submit dropdown to loading', async () => {
factory();
@@ -66,4 +87,14 @@ describe('Batch comments submit dropdown', () => {
expect(findSubmitButton().props('loading')).toBe(true);
});
+
+ it.each`
+ canApprove | exists | existsText
+ ${true} | ${true} | ${'shows'}
+ ${false} | ${false} | ${'hides'}
+ `('$existsText approve checkbox if can_approve is $canApprove', ({ canApprove, exists }) => {
+ factory({ canApprove });
+
+ expect(wrapper.findByTestId('approve_merge_request').exists()).toBe(exists);
+ });
});
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 9f50b12bac2..6369ea9aa15 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
@@ -180,6 +180,7 @@ describe('Batch comments store actions', () => {
});
it('calls service with notes data', () => {
+ mock.onAny().reply(200);
jest.spyOn(axios, 'post');
return actions
@@ -192,7 +193,7 @@ describe('Batch comments store actions', () => {
it('dispatches error commits', () => {
mock.onAny().reply(500);
- return actions.publishReview({ dispatch, commit, getters, rootGetters }).then(() => {
+ return actions.publishReview({ dispatch, commit, getters, rootGetters }).catch(() => {
expect(commit.mock.calls[0]).toEqual(['REQUEST_PUBLISH_REVIEW']);
expect(commit.mock.calls[1]).toEqual(['RECEIVE_PUBLISH_REVIEW_ERROR']);
});
diff --git a/spec/frontend/behaviors/bind_in_out_spec.js b/spec/frontend/behaviors/bind_in_out_spec.js
index 49425a9377e..4d958e30b4d 100644
--- a/spec/frontend/behaviors/bind_in_out_spec.js
+++ b/spec/frontend/behaviors/bind_in_out_spec.js
@@ -33,7 +33,7 @@ describe('BindInOut', () => {
testContext.bindInOut = new BindInOut({ tagName: 'INPUT' });
});
- it('should set .eventType to keyup ', () => {
+ it('should set .eventType to keyup', () => {
expect(testContext.bindInOut.eventType).toEqual('keyup');
});
});
@@ -43,7 +43,7 @@ describe('BindInOut', () => {
testContext.bindInOut = new BindInOut({ tagName: 'TEXTAREA' });
});
- it('should set .eventType to keyup ', () => {
+ it('should set .eventType to keyup', () => {
expect(testContext.bindInOut.eventType).toEqual('keyup');
});
});
@@ -53,7 +53,7 @@ describe('BindInOut', () => {
testContext.bindInOut = new BindInOut({ tagName: 'SELECT' });
});
- it('should set .eventType to change ', () => {
+ it('should set .eventType to change', () => {
expect(testContext.bindInOut.eventType).toEqual('change');
});
});
diff --git a/spec/frontend/blob/sketch/index_spec.js b/spec/frontend/blob/sketch/index_spec.js
index e8d1f724c4b..4b6cb79791c 100644
--- a/spec/frontend/blob/sketch/index_spec.js
+++ b/spec/frontend/blob/sketch/index_spec.js
@@ -2,18 +2,6 @@ import SketchLoader from '~/blob/sketch';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
-jest.mock('jszip', () => {
- return {
- loadAsync: jest.fn().mockResolvedValue({
- files: {
- 'previews/preview.png': {
- async: jest.fn().mockResolvedValue('foo'),
- },
- },
- }),
- };
-});
-
describe('Sketch viewer', () => {
beforeEach(() => {
loadHTMLFixture('static/sketch_viewer.html');
@@ -25,7 +13,7 @@ describe('Sketch viewer', () => {
describe('with error message', () => {
beforeEach(() => {
- jest.spyOn(SketchLoader.prototype, 'getZipFile').mockImplementation(
+ jest.spyOn(SketchLoader.prototype, 'getZipContents').mockImplementation(
() =>
new Promise((resolve, reject) => {
reject();
@@ -50,7 +38,13 @@ describe('Sketch viewer', () => {
describe('success', () => {
beforeEach(() => {
- jest.spyOn(SketchLoader.prototype, 'getZipFile').mockResolvedValue();
+ jest.spyOn(SketchLoader.prototype, 'getZipContents').mockResolvedValue({
+ files: {
+ 'previews/preview.png': {
+ async: jest.fn().mockResolvedValue('foo'),
+ },
+ },
+ });
// eslint-disable-next-line no-new
new SketchLoader(document.getElementById('js-sketch-viewer'));
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index 985902b4a3b..2c3ec69f9ae 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -7,6 +7,8 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
+import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import { issuableTypes } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import defaultStore from '~/boards/stores';
@@ -47,6 +49,8 @@ describe('Board card component', () => {
const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight');
const findEpicProgressTooltip = () => wrapper.findByTestId('epic-progress-tooltip-content');
const findHiddenIssueIcon = () => wrapper.findByTestId('hidden-icon');
+ const findMoveToPositionComponent = () => wrapper.findComponent(BoardCardMoveToPosition);
+ const findWorkItemIcon = () => wrapper.findComponent(WorkItemTypeIcon);
const performSearchMock = jest.fn();
@@ -75,10 +79,12 @@ describe('Board card component', () => {
propsData: {
list,
item: issue,
+ index: 0,
...props,
},
stubs: {
GlLoadingIcon: true,
+ BoardCardMoveToPosition: true,
},
directives: {
GlTooltip: createMockDirective(),
@@ -137,6 +143,20 @@ describe('Board card component', () => {
expect(findHiddenIssueIcon().exists()).toBe(false);
});
+ it('renders the move to position icon', () => {
+ expect(findMoveToPositionComponent().exists()).toBe(true);
+ });
+
+ it('does not render the work type icon by default', () => {
+ expect(findWorkItemIcon().exists()).toBe(false);
+ });
+
+ it('renders the work type icon when props is passed', () => {
+ createWrapper({ item: issue, list, showWorkItemTypeIcon: true });
+ expect(findWorkItemIcon().exists()).toBe(true);
+ expect(findWorkItemIcon().props('workItemType')).toBe(issue.type);
+ });
+
it('renders issue ID with #', () => {
expect(wrapper.find('.board-card-number').text()).toContain(`#${issue.iid}`);
});
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index 04192489817..65a41c49e7f 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -75,6 +75,7 @@ export default function createComponent({
id: 1,
iid: 1,
confidential: false,
+ referencePath: 'gitlab-org/test-subgroup/gitlab-test#1',
labels: [],
assignees: [],
...listIssueProps,
diff --git a/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap
index 3fb0706fd10..34e4f996ff0 100644
--- a/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap
+++ b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BoardBlockedIcon on mouseenter on blocked icon with more than three blocking issues matches the snapshot 1`] = `
-"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-icon s16\\" id=\\"blocked-icon-uniqueId\\">
+"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-text-red-500 gl-icon s16\\" id=\\"blocked-icon-uniqueId\\">
<use href=\\"#issue-block\\"></use>
</svg>
<div class=\\"gl-popover\\">
diff --git a/spec/frontend/boards/components/board_blocked_icon_spec.js b/spec/frontend/boards/components/board_blocked_icon_spec.js
index cf4ba07da16..ffdc0a7cecc 100644
--- a/spec/frontend/boards/components/board_blocked_icon_spec.js
+++ b/spec/frontend/boards/components/board_blocked_icon_spec.js
@@ -10,13 +10,17 @@ import { blockingIssuablesQueries, issuableTypes } from '~/boards/constants';
import { truncate } from '~/lib/utils/text_utility';
import {
mockIssue,
+ mockEpic,
mockBlockingIssue1,
mockBlockingIssue2,
+ mockBlockingEpic1,
mockBlockingIssuablesResponse1,
mockBlockingIssuablesResponse2,
mockBlockingIssuablesResponse3,
mockBlockedIssue1,
mockBlockedIssue2,
+ mockBlockedEpic1,
+ mockBlockingEpicIssuablesResponse1,
} from '../mock_data';
describe('BoardBlockedIcon', () => {
@@ -51,9 +55,11 @@ describe('BoardBlockedIcon', () => {
const createWrapperWithApollo = ({
item = mockBlockedIssue1,
blockingIssuablesSpy = jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1),
+ issuableItem = mockIssue,
+ issuableType = issuableTypes.issue,
} = {}) => {
mockApollo = createMockApollo([
- [blockingIssuablesQueries[issuableTypes.issue].query, blockingIssuablesSpy],
+ [blockingIssuablesQueries[issuableType].query, blockingIssuablesSpy],
]);
Vue.use(VueApollo);
@@ -62,27 +68,34 @@ describe('BoardBlockedIcon', () => {
apolloProvider: mockApollo,
propsData: {
item: {
- ...mockIssue,
+ ...issuableItem,
...item,
},
uniqueId: 'uniqueId',
- issuableType: issuableTypes.issue,
+ issuableType,
},
attachTo: document.body,
}),
);
};
- const createWrapper = ({ item = {}, queries = {}, data = {}, loading = false } = {}) => {
+ const createWrapper = ({
+ item = {},
+ queries = {},
+ data = {},
+ loading = false,
+ mockIssuable = mockIssue,
+ issuableType = issuableTypes.issue,
+ } = {}) => {
wrapper = extendedWrapper(
shallowMount(BoardBlockedIcon, {
propsData: {
item: {
- ...mockIssue,
+ ...mockIssuable,
...item,
},
uniqueId: 'uniqueid',
- issuableType: issuableTypes.issue,
+ issuableType,
},
data() {
return {
@@ -105,11 +118,24 @@ describe('BoardBlockedIcon', () => {
);
};
- it('should render blocked icon', () => {
- createWrapper();
+ it.each`
+ mockIssuable | issuableType | expectedIcon
+ ${mockIssue} | ${issuableTypes.issue} | ${'issue-block'}
+ ${mockEpic} | ${issuableTypes.epic} | ${'entity-blocked'}
+ `(
+ 'should render blocked icon for $issuableType',
+ ({ mockIssuable, issuableType, expectedIcon }) => {
+ createWrapper({
+ mockIssuable,
+ issuableType,
+ });
- expect(findGlIcon().exists()).toBe(true);
- });
+ expect(findGlIcon().exists()).toBe(true);
+ const icon = findGlIcon();
+ expect(icon.exists()).toBe(true);
+ expect(icon.props('name')).toBe(expectedIcon);
+ },
+ );
it('should display a loading spinner while loading', () => {
createWrapper({ loading: true });
@@ -124,17 +150,29 @@ describe('BoardBlockedIcon', () => {
});
describe('on mouseenter on blocked icon', () => {
- it('should query for blocking issuables and render the result', async () => {
- createWrapperWithApollo();
+ it.each`
+ item | issuableType | mockBlockingIssuable | issuableItem | blockingIssuablesSpy
+ ${mockBlockedIssue1} | ${issuableTypes.issue} | ${mockBlockingIssue1} | ${mockIssue} | ${jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1)}
+ ${mockBlockedEpic1} | ${issuableTypes.epic} | ${mockBlockingEpic1} | ${mockEpic} | ${jest.fn().mockResolvedValue(mockBlockingEpicIssuablesResponse1)}
+ `(
+ 'should query for blocking issuables and render the result for $issuableType',
+ async ({ item, issuableType, issuableItem, mockBlockingIssuable, blockingIssuablesSpy }) => {
+ createWrapperWithApollo({
+ item,
+ issuableType,
+ issuableItem,
+ blockingIssuablesSpy,
+ });
- expect(findGlPopover().text()).not.toContain(mockBlockingIssue1.title);
+ expect(findGlPopover().text()).not.toContain(mockBlockingIssuable.title);
- await mouseenter();
+ await mouseenter();
- expect(findGlPopover().exists()).toBe(true);
- expect(findIssuableTitle().text()).toContain(mockBlockingIssue1.title);
- expect(wrapper.vm.skip).toBe(true);
- });
+ expect(findGlPopover().exists()).toBe(true);
+ expect(findIssuableTitle().text()).toContain(mockBlockingIssuable.title);
+ expect(wrapper.vm.skip).toBe(true);
+ },
+ );
it('should emit "blocking-issuables-error" event on query error', async () => {
const mockError = new Error('mayday');
diff --git a/spec/frontend/boards/components/board_card_move_to_position_spec.js b/spec/frontend/boards/components/board_card_move_to_position_spec.js
new file mode 100644
index 00000000000..7254b9486ef
--- /dev/null
+++ b/spec/frontend/boards/components/board_card_move_to_position_spec.js
@@ -0,0 +1,133 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+
+import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
+import { mockList, mockIssue2, mockIssue, mockIssue3, mockIssue4 } from 'jest/boards/mock_data';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+
+Vue.use(Vuex);
+
+const dropdownOptions = [
+ BoardCardMoveToPosition.i18n.moveToStartText,
+ BoardCardMoveToPosition.i18n.moveToEndText,
+];
+
+describe('Board Card Move to position', () => {
+ let wrapper;
+ let trackingSpy;
+ let store;
+ let dispatch;
+ const itemIndex = 1;
+
+ const createStoreOptions = () => {
+ const state = {
+ pageInfoByListId: {
+ 'gid://gitlab/List/1': {},
+ 'gid://gitlab/List/2': { hasNextPage: true },
+ },
+ };
+ const getters = {
+ getBoardItemsByList: () => () => [mockIssue, mockIssue2, mockIssue3, mockIssue4],
+ };
+ const actions = {
+ moveItem: jest.fn(),
+ };
+
+ return {
+ state,
+ getters,
+ actions,
+ };
+ };
+
+ const createComponent = (propsData) => {
+ wrapper = shallowMount(BoardCardMoveToPosition, {
+ store,
+ propsData: {
+ item: mockIssue2,
+ list: mockList,
+ index: 0,
+ ...propsData,
+ },
+ stubs: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ store = new Vuex.Store(createStoreOptions());
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findMoveToPositionDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItems = () => findMoveToPositionDropdown().findAllComponents(GlDropdownItem);
+ const findDropdownItemAtIndex = (index) => findDropdownItems().at(index);
+
+ describe('Dropdown', () => {
+ describe('Dropdown button', () => {
+ it('has an icon with vertical ellipsis', () => {
+ expect(findMoveToPositionDropdown().exists()).toBe(true);
+ expect(findMoveToPositionDropdown().props('icon')).toBe('ellipsis_v');
+ });
+
+ it('is opened on the click of vertical ellipsis and has 2 dropdown items when number of list items < 10', () => {
+ findMoveToPositionDropdown().vm.$emit('click');
+ expect(findDropdownItems()).toHaveLength(dropdownOptions.length);
+ });
+ });
+
+ describe('Dropdown options', () => {
+ beforeEach(() => {
+ createComponent({ index: itemIndex });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ dispatch = jest.spyOn(store, 'dispatch').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it.each`
+ dropdownIndex | dropdownLabel | trackLabel | positionInList
+ ${0} | ${BoardCardMoveToPosition.i18n.moveToStartText} | ${'move_to_start'} | ${0}
+ ${1} | ${BoardCardMoveToPosition.i18n.moveToEndText} | ${'move_to_end'} | ${-1}
+ `(
+ 'on click of dropdown index $dropdownIndex with label $dropdownLabel should call moveItem action with tracking label $trackLabel',
+ async ({ dropdownIndex, dropdownLabel, trackLabel, positionInList }) => {
+ await findMoveToPositionDropdown().vm.$emit('click');
+
+ expect(findDropdownItemAtIndex(dropdownIndex).text()).toBe(dropdownLabel);
+ await findDropdownItemAtIndex(dropdownIndex).vm.$emit('click', {
+ stopPropagation: () => {},
+ });
+
+ await nextTick();
+
+ expect(trackingSpy).toHaveBeenCalledWith('boards:list', 'click_toggle_button', {
+ category: 'boards:list',
+ label: trackLabel,
+ property: 'type_card',
+ });
+ expect(dispatch).toHaveBeenCalledWith('moveItem', {
+ fromListId: mockList.id,
+ itemId: mockIssue2.id,
+ itemIid: mockIssue2.iid,
+ itemPath: mockIssue2.referencePath,
+ positionInList,
+ toListId: mockList.id,
+ allItemsLoadedInList: true,
+ atIndex: itemIndex,
+ });
+ },
+ );
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index bb1e63a581e..2feaa5dff8c 100644
--- a/spec/frontend/boards/components/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -1,5 +1,5 @@
import { GlLabel } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
@@ -45,7 +45,10 @@ describe('Board card', () => {
item = mockIssue,
} = {}) => {
wrapper = mountFn(BoardCard, {
- stubs,
+ stubs: {
+ ...stubs,
+ BoardCardInner,
+ },
store,
propsData: {
list: mockLabelList,
@@ -86,7 +89,7 @@ describe('Board card', () => {
describe('when GlLabel is clicked in BoardCardInner', () => {
it('doesnt call toggleBoardItem', () => {
createStore({ initialState: { isShowingLabels: true } });
- mountComponent({ mountFn: mount, stubs: {} });
+ mountComponent();
wrapper.findComponent(GlLabel).trigger('mouseup');
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index 8b0100d069a..f097f42476a 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -90,7 +90,7 @@ describe('Issue boards new issue form', () => {
});
});
- it('it uses the first issue ID as moveAfterId', async () => {
+ it('uses the first issue ID as moveAfterId', async () => {
findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
await nextTick();
diff --git a/spec/frontend/boards/components/issue_due_date_spec.js b/spec/frontend/boards/components/issue_due_date_spec.js
index 73340c1b96b..45fa10bf03a 100644
--- a/spec/frontend/boards/components/issue_due_date_spec.js
+++ b/spec/frontend/boards/components/issue_due_date_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import dateFormat from 'dateformat';
import IssueDueDate from '~/boards/components/issue_due_date.vue';
+import dateFormat from '~/lib/dateformat';
const createComponent = (dueDate = new Date(), closed = false) =>
shallowMount(IssueDueDate, {
diff --git a/spec/frontend/boards/components/item_count_spec.js b/spec/frontend/boards/components/item_count_spec.js
index 06cd3910fc0..0c0c7f66933 100644
--- a/spec/frontend/boards/components/item_count_spec.js
+++ b/spec/frontend/boards/components/item_count_spec.js
@@ -50,7 +50,7 @@ describe('IssueCount', () => {
});
it('contains maxIssueCount in the template', () => {
- expect(vm.find('.max-issue-size').text()).toEqual(String(maxIssueCount));
+ expect(vm.find('.max-issue-size').text()).toContain(String(maxIssueCount));
});
it('does not have text-danger class when issueSize is less than maxIssueCount', () => {
@@ -75,7 +75,7 @@ describe('IssueCount', () => {
});
it('contains maxIssueCount in the template', () => {
- expect(vm.find('.max-issue-size').text()).toEqual(String(maxIssueCount));
+ expect(vm.find('.max-issue-size').text()).toContain(String(maxIssueCount));
});
it('has text-danger class', () => {
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 1ee05d81f37..dc1f3246be0 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -262,9 +262,11 @@ export const rawIssue = {
epic: {
id: 'gid://gitlab/Epic/41',
},
+ type: 'ISSUE',
};
export const mockIssueFullPath = 'gitlab-org/test-subgroup/gitlab-test';
+export const mockEpicFullPath = 'gitlab-org/test-subgroup';
export const mockIssue = {
id: 'gid://gitlab/Issue/436',
@@ -287,6 +289,48 @@ export const mockIssue = {
epic: {
id: 'gid://gitlab/Epic/41',
},
+ type: 'ISSUE',
+};
+
+export const mockEpic = {
+ id: 'gid://gitlab/Epic/26',
+ iid: '1',
+ group: {
+ id: 'gid://gitlab/Group/33',
+ fullPath: 'twitter',
+ __typename: 'Group',
+ },
+ title: 'Eum animi debitis occaecati ad non odio repellat voluptatem similique.',
+ state: 'opened',
+ reference: '&1',
+ referencePath: `${mockEpicFullPath}&1`,
+ webPath: `/groups/${mockEpicFullPath}/-/epics/1`,
+ webUrl: `${mockEpicFullPath}/-/epics/1`,
+ createdAt: '2022-01-18T05:15:15Z',
+ closedAt: null,
+ __typename: 'Epic',
+ relativePosition: null,
+ confidential: false,
+ subscribed: true,
+ blocked: true,
+ blockedByCount: 1,
+ labels: {
+ nodes: [],
+ __typename: 'LabelConnection',
+ },
+ hasIssues: true,
+ descendantCounts: {
+ closedEpics: 0,
+ closedIssues: 0,
+ openedEpics: 0,
+ openedIssues: 2,
+ __typename: 'EpicDescendantCount',
+ },
+ descendantWeightSum: {
+ closedIssues: 0,
+ openedIssues: 0,
+ __typename: 'EpicDescendantWeights',
+ },
};
export const mockActiveIssue = {
@@ -521,6 +565,15 @@ export const mockBlockingIssue1 = {
__typename: 'Issue',
};
+export const mockBlockingEpic1 = {
+ id: 'gid://gitlab/Epic/29',
+ iid: '4',
+ title: 'Sint nihil exercitationem aspernatur unde molestiae rem accusantium.',
+ reference: 'twitter&4',
+ webUrl: 'http://gdk.test:3000/groups/gitlab-org/test-subgroup/-/epics/4',
+ __typename: 'Epic',
+};
+
export const mockBlockingIssue2 = {
id: 'gid://gitlab/Issue/524',
iid: '5',
@@ -562,6 +615,23 @@ export const mockBlockingIssuablesResponse1 = {
},
};
+export const mockBlockingEpicIssuablesResponse1 = {
+ data: {
+ group: {
+ __typename: 'Group',
+ id: 'gid://gitlab/Group/33',
+ issuable: {
+ __typename: 'Epic',
+ id: 'gid://gitlab/Epic/26',
+ blockingIssuables: {
+ __typename: 'EpicConnection',
+ nodes: [mockBlockingEpic1],
+ },
+ },
+ },
+ },
+};
+
export const mockBlockingIssuablesResponse2 = {
data: {
issuable: {
@@ -599,6 +669,12 @@ export const mockBlockedIssue2 = {
webUrl: 'http://gdk.test:3000/gitlab-org/my-project-1/-/issues/0',
};
+export const mockBlockedEpic1 = {
+ id: '26',
+ blockedByCount: 1,
+ webUrl: 'http://gdk.test:3000/gitlab-org/test-subgroup/-/epics/1',
+};
+
export const mockMoveIssueParams = {
itemId: 1,
fromListId: 'gid://gitlab/List/1',
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index e48b946ff1b..e919300228a 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1056,6 +1056,8 @@ describe('moveIssueCard and undoMoveIssueCard', () => {
originalIndex = 0,
moveBeforeId = undefined,
moveAfterId = undefined,
+ allItemsLoadedInList = true,
+ listPosition = undefined,
} = {}) => {
state = {
boardLists: {
@@ -1065,12 +1067,28 @@ describe('moveIssueCard and undoMoveIssueCard', () => {
boardItems: { [itemId]: originalIssue },
boardItemsByListId: { [fromListId]: [123] },
};
- params = { itemId, fromListId, toListId, moveBeforeId, moveAfterId };
+ params = {
+ itemId,
+ fromListId,
+ toListId,
+ moveBeforeId,
+ moveAfterId,
+ listPosition,
+ allItemsLoadedInList,
+ };
moveMutations = [
{ type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } },
{
type: types.ADD_BOARD_ITEM_TO_LIST,
- payload: { itemId, listId: toListId, moveBeforeId, moveAfterId },
+ payload: {
+ itemId,
+ listId: toListId,
+ moveBeforeId,
+ moveAfterId,
+ listPosition,
+ allItemsLoadedInList,
+ atIndex: originalIndex,
+ },
},
];
undoMutations = [
@@ -1366,9 +1384,17 @@ describe('updateIssueOrder', () => {
state,
[
{
+ type: types.MUTATE_ISSUE_IN_PROGRESS,
+ payload: true,
+ },
+ {
type: types.MUTATE_ISSUE_SUCCESS,
payload: { issue: rawIssue },
},
+ {
+ type: types.MUTATE_ISSUE_IN_PROGRESS,
+ payload: false,
+ },
],
[],
);
@@ -1390,6 +1416,14 @@ describe('updateIssueOrder', () => {
state,
[
{
+ type: types.MUTATE_ISSUE_IN_PROGRESS,
+ payload: true,
+ },
+ {
+ type: types.MUTATE_ISSUE_IN_PROGRESS,
+ payload: false,
+ },
+ {
type: types.SET_ERROR,
payload: 'An error occurred while moving the issue. Please try again.',
},
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index 1606ca09d8f..87a183c0441 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -513,6 +513,31 @@ describe('Board Store Mutations', () => {
listState: [mockIssue2.id, mockIssue.id],
},
],
+ [
+ 'to the top of the list',
+ {
+ payload: {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ positionInList: 0,
+ atIndex: 1,
+ },
+ listState: [mockIssue2.id, mockIssue.id],
+ },
+ ],
+ [
+ 'to the bottom of the list when the list is fully loaded',
+ {
+ payload: {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ positionInList: -1,
+ atIndex: 0,
+ allItemsLoadedInList: true,
+ },
+ listState: [mockIssue.id, mockIssue2.id],
+ },
+ ],
])(`inserts an item into a list %s`, (_, { payload, listState }) => {
mutations.ADD_BOARD_ITEM_TO_LIST(state, payload);
diff --git a/spec/frontend/branches/components/divergence_graph_spec.js b/spec/frontend/branches/components/divergence_graph_spec.js
index 3b565539f87..9429a6e982c 100644
--- a/spec/frontend/branches/components/divergence_graph_spec.js
+++ b/spec/frontend/branches/components/divergence_graph_spec.js
@@ -21,7 +21,7 @@ describe('Branch divergence graph component', () => {
maxCommits: 100,
});
- expect(vm.findAll(GraphBar).length).toBe(2);
+ expect(vm.findAllComponents(GraphBar).length).toBe(2);
expect(vm.element).toMatchSnapshot();
});
@@ -45,7 +45,7 @@ describe('Branch divergence graph component', () => {
maxCommits: 100,
});
- expect(vm.findAll(GraphBar).length).toBe(1);
+ expect(vm.findAllComponents(GraphBar).length).toBe(1);
expect(vm.element).toMatchSnapshot();
});
diff --git a/spec/frontend/captcha/captcha_modal_spec.js b/spec/frontend/captcha/captcha_modal_spec.js
index b8448f9ff0a..20e69b5a834 100644
--- a/spec/frontend/captcha/captcha_modal_spec.js
+++ b/spec/frontend/captcha/captcha_modal_spec.js
@@ -40,7 +40,7 @@ describe('Captcha Modal', () => {
});
const findGlModal = () => {
- const glModal = wrapper.find(GlModal);
+ const glModal = wrapper.findComponent(GlModal);
jest.spyOn(glModal.vm, 'show').mockImplementation(() => glModal.vm.$emit('shown'));
jest
diff --git a/spec/frontend/cascading_settings/components/lock_popovers_spec.js b/spec/frontend/cascading_settings/components/lock_popovers_spec.js
index 182e3c1c8ff..9d3275a1ff2 100644
--- a/spec/frontend/cascading_settings/components/lock_popovers_spec.js
+++ b/spec/frontend/cascading_settings/components/lock_popovers_spec.js
@@ -39,7 +39,7 @@ describe('LockPopovers', () => {
wrapper = mountExtended(LockPopovers);
};
- const findPopover = () => extendedWrapper(wrapper.find(GlPopover));
+ const findPopover = () => extendedWrapper(wrapper.findComponent(GlPopover));
const findByTextInPopover = (text, options) =>
findPopover().findByText((_, element) => element.textContent === text, options);
@@ -143,7 +143,7 @@ describe('LockPopovers', () => {
});
it('mounts multiple popovers', () => {
- const popovers = wrapper.findAll(GlPopover).wrappers;
+ const popovers = wrapper.findAllComponents(GlPopover).wrappers;
expectCorrectPopoverTarget(popoverMountEl1, popovers[0]);
expectCorrectPopoverTarget(popoverMountEl2, popovers[1]);
diff --git a/spec/frontend/chronic_duration_spec.js b/spec/frontend/chronic_duration_spec.js
index 32652e13dfc..b063110782a 100644
--- a/spec/frontend/chronic_duration_spec.js
+++ b/spec/frontend/chronic_duration_spec.js
@@ -86,7 +86,7 @@ describe('parseChronicDuration', () => {
describe('when .raiseExceptions set to true', () => {
it('raises with DurationParseError', () => {
- expect(() => parseChronicDuration('23 gobblygoos', { raiseExceptions: true })).toThrowError(
+ expect(() => parseChronicDuration('23 gobblygoos', { raiseExceptions: true })).toThrow(
DurationParseError,
);
});
diff --git a/spec/frontend/ci_lint/components/ci_lint_spec.js b/spec/frontend/ci_lint/components/ci_lint_spec.js
index 0ad6ed56b0e..ea69a80274e 100644
--- a/spec/frontend/ci_lint/components/ci_lint_spec.js
+++ b/spec/frontend/ci_lint/components/ci_lint_spec.js
@@ -36,9 +36,9 @@ describe('CI Lint', () => {
});
};
- const findEditor = () => wrapper.find(SourceEditor);
- const findAlert = () => wrapper.find(GlAlert);
- const findCiLintResults = () => wrapper.find(CiLintResults);
+ const findEditor = () => wrapper.findComponent(SourceEditor);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findCiLintResults = () => wrapper.findComponent(CiLintResults);
const findValidateBtn = () => wrapper.find('[data-testid="ci-lint-validate"]');
const findClearBtn = () => wrapper.find('[data-testid="ci-lint-clear"]');
diff --git a/spec/frontend/ci_secure_files/components/secure_files_list_spec.js b/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
index 04d38a3281a..5273aafbb04 100644
--- a/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
+++ b/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
@@ -83,7 +83,9 @@ describe('SecureFilesList', () => {
const [secureFile] = secureFiles;
expect(findCell(0, 0).text()).toBe(secureFile.name);
- expect(findCell(0, 1).find(TimeAgoTooltip).props('time')).toBe(secureFile.created_at);
+ expect(findCell(0, 1).findComponent(TimeAgoTooltip).props('time')).toBe(
+ secureFile.created_at,
+ );
});
describe('event tracking', () => {
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 6bf28a67300..01eb08f4ece 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
@@ -16,13 +16,13 @@ describe('TriggersList', () => {
});
};
- const findTable = () => wrapper.find(GlTable);
+ const findTable = () => wrapper.findComponent(GlTable);
const findHeaderAt = (i) => wrapper.findAll('thead th').at(i);
const findRows = () => wrapper.findAll('tbody tr');
const findRowAt = (i) => findRows().at(i);
const findCell = (i, col) => findRowAt(i).findAll('td').at(col);
- const findClipboardBtn = (i) => findCell(i, 0).find(ClipboardButton);
- const findInvalidBadge = (i) => findCell(i, 0).find(GlBadge);
+ const findClipboardBtn = (i) => findCell(i, 0).findComponent(ClipboardButton);
+ const findInvalidBadge = (i) => findCell(i, 0).findComponent(GlBadge);
const findEditBtn = (i) => findRowAt(i).find('[data-testid="edit-btn"]');
const findRevokeBtn = (i) => findRowAt(i).find('[data-testid="trigger_revoke_button"]');
@@ -65,17 +65,19 @@ describe('TriggersList', () => {
it('displays a time ago label when last used', () => {
expect(findCell(0, 3).text()).toBe('Never');
- expect(findCell(1, 3).find(TimeAgoTooltip).props('time')).toBe(triggers[1].lastUsed);
+ expect(findCell(1, 3).findComponent(TimeAgoTooltip).props('time')).toBe(triggers[1].lastUsed);
});
it('displays actions in a rows', () => {
const [data] = triggers;
+ const confirmWarning =
+ 'By revoking a trigger you will break any processes making use of it. Are you sure?';
expect(findEditBtn(0).attributes('href')).toBe(data.editProjectTriggerPath);
expect(findRevokeBtn(0).attributes('href')).toBe(data.projectTriggerPath);
expect(findRevokeBtn(0).attributes('data-method')).toBe('delete');
- expect(findRevokeBtn(0).attributes('data-confirm')).toBeTruthy();
+ expect(findRevokeBtn(0).attributes('data-confirm')).toBe(confirmWarning);
});
describe('when there are no triggers set', () => {
diff --git a/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js
new file mode 100644
index 00000000000..867f8e0cf8f
--- /dev/null
+++ b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js
@@ -0,0 +1,215 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon, GlTable } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import { resolvers } from '~/ci_variable_list/graphql/resolvers';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+
+import ciProjectVariables from '~/ci_variable_list/components/ci_project_variables.vue';
+import ciVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue';
+import ciVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
+import getProjectEnvironments from '~/ci_variable_list/graphql/queries/project_environments.query.graphql';
+import getProjectVariables from '~/ci_variable_list/graphql/queries/project_variables.query.graphql';
+
+import addProjectVariable from '~/ci_variable_list/graphql/mutations/project_add_variable.mutation.graphql';
+import deleteProjectVariable from '~/ci_variable_list/graphql/mutations/project_delete_variable.mutation.graphql';
+import updateProjectVariable from '~/ci_variable_list/graphql/mutations/project_update_variable.mutation.graphql';
+
+import {
+ environmentFetchErrorText,
+ genericMutationErrorText,
+ variableFetchErrorText,
+} from '~/ci_variable_list/constants';
+
+import {
+ devName,
+ mockProjectEnvironments,
+ mockProjectVariables,
+ newVariable,
+ prodName,
+} from '../mocks';
+
+jest.mock('~/flash');
+
+Vue.use(VueApollo);
+
+const mockProvide = {
+ endpoint: '/variables',
+ projectFullPath: '/namespace/project',
+ projectId: 1,
+};
+
+describe('Ci Project Variable list', () => {
+ let wrapper;
+
+ let mockApollo;
+ let mockEnvironments;
+ let mockVariables;
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findCiTable = () => wrapper.findComponent(GlTable);
+ const findCiSettings = () => wrapper.findComponent(ciVariableSettings);
+
+ // eslint-disable-next-line consistent-return
+ const createComponentWithApollo = async ({ isLoading = false } = {}) => {
+ const handlers = [
+ [getProjectEnvironments, mockEnvironments],
+ [getProjectVariables, mockVariables],
+ ];
+
+ mockApollo = createMockApollo(handlers, resolvers);
+
+ wrapper = shallowMount(ciProjectVariables, {
+ provide: mockProvide,
+ apolloProvider: mockApollo,
+ stubs: { ciVariableSettings, ciVariableTable },
+ });
+
+ if (!isLoading) {
+ return waitForPromises();
+ }
+ };
+
+ beforeEach(() => {
+ mockEnvironments = jest.fn();
+ mockVariables = jest.fn();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('while queries are being fetch', () => {
+ beforeEach(() => {
+ createComponentWithApollo({ isLoading: true });
+ });
+
+ it('shows a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findCiTable().exists()).toBe(false);
+ });
+ });
+
+ describe('when queries are resolved', () => {
+ describe('successfuly', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockResolvedValue(mockProjectVariables);
+
+ await createComponentWithApollo();
+ });
+
+ it('passes down the expected environments as props', () => {
+ expect(findCiSettings().props('environments')).toEqual([prodName, devName]);
+ });
+
+ it('passes down the expected variables as props', () => {
+ expect(findCiSettings().props('variables')).toEqual(
+ mockProjectVariables.data.project.ciVariables.nodes,
+ );
+ });
+
+ it('createFlash was not called', () => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with an error for variables', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockRejectedValue();
+
+ await createComponentWithApollo();
+ });
+
+ it('calls createFlash with the expected error message', () => {
+ expect(createFlash).toHaveBeenCalledWith({ message: variableFetchErrorText });
+ });
+ });
+
+ describe('with an error for environments', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockRejectedValue();
+ mockVariables.mockResolvedValue(mockProjectVariables);
+
+ await createComponentWithApollo();
+ });
+
+ it('calls createFlash with the expected error message', () => {
+ expect(createFlash).toHaveBeenCalledWith({ message: environmentFetchErrorText });
+ });
+ });
+ });
+
+ describe('mutations', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockResolvedValue(mockProjectVariables);
+
+ await createComponentWithApollo();
+ });
+ it.each`
+ actionName | mutation | event
+ ${'add'} | ${addProjectVariable} | ${'add-variable'}
+ ${'update'} | ${updateProjectVariable} | ${'update-variable'}
+ ${'delete'} | ${deleteProjectVariable} | ${'delete-variable'}
+ `(
+ 'calls the right mutation when user performs $actionName variable',
+ async ({ event, mutation }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation,
+ variables: {
+ endpoint: mockProvide.endpoint,
+ fullPath: mockProvide.projectFullPath,
+ projectId: convertToGraphQLId('Project', mockProvide.projectId),
+ variable: newVariable,
+ },
+ });
+ },
+ );
+
+ it.each`
+ actionName | event | mutationName
+ ${'add'} | ${'add-variable'} | ${'addProjectVariable'}
+ ${'update'} | ${'update-variable'} | ${'updateProjectVariable'}
+ ${'delete'} | ${'delete-variable'} | ${'deleteProjectVariable'}
+ `(
+ 'throws with the specific graphql error if present when user performs $actionName variable',
+ async ({ event, mutationName }) => {
+ const graphQLErrorMessage = 'There is a problem with this graphQL action';
+ jest
+ .spyOn(wrapper.vm.$apollo, 'mutate')
+ .mockResolvedValue({ data: { [mutationName]: { errors: [graphQLErrorMessage] } } });
+ await findCiSettings().vm.$emit(event, newVariable);
+ await nextTick();
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: graphQLErrorMessage });
+ },
+ );
+
+ it.each`
+ actionName | event
+ ${'add'} | ${'add-variable'}
+ ${'update'} | ${'update-variable'}
+ ${'delete'} | ${'delete-variable'}
+ `(
+ 'throws generic error when the mutation fails with no graphql errors and user performs $actionName variable',
+ async ({ event }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => {
+ throw new Error();
+ });
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: genericMutationErrorText });
+ },
+ );
+ });
+});
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 e5019e3261e..1ea4e4f833b 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
@@ -11,6 +11,7 @@ import {
EVENT_ACTION,
ENVIRONMENT_SCOPE_LINK_TITLE,
instanceString,
+ variableOptions,
} from '~/ci_variable_list/constants';
import { mockVariablesWithScopes } from '../mocks';
import ModalStub from '../stubs';
@@ -57,21 +58,23 @@ describe('Ci variable modal', () => {
});
};
- const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
+ const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown);
const findReferenceWarning = () => wrapper.findByTestId('contains-variable-reference');
- const findModal = () => wrapper.find(ModalStub);
+ const findModal = () => wrapper.findComponent(ModalStub);
const findAWSTip = () => wrapper.findByTestId('aws-guidance-tip');
const findAddorUpdateButton = () => wrapper.findByTestId('ciUpdateOrAddVariableBtn');
const deleteVariableButton = () =>
findModal()
- .findAll(GlButton)
+ .findAllComponents(GlButton)
.wrappers.find((button) => button.props('variant') === 'danger');
const findProtectedVariableCheckbox = () =>
wrapper.findByTestId('ci-variable-protected-checkbox');
const findMaskedVariableCheckbox = () => wrapper.findByTestId('ci-variable-masked-checkbox');
const findValueField = () => wrapper.find('#ci-variable-value');
const findEnvScopeLink = () => wrapper.findByTestId('environment-scope-link');
- const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').find(GlFormInput);
+ const findEnvScopeInput = () =>
+ wrapper.findByTestId('environment-scope').findComponent(GlFormInput);
+ const findVariableTypeDropdown = () => wrapper.find('#ci-variable-type');
afterEach(() => {
wrapper.destroy();
@@ -83,7 +86,7 @@ describe('Ci variable modal', () => {
createComponent();
});
- it('shows the submit button as disabled ', () => {
+ it('shows the submit button as disabled', () => {
expect(findAddorUpdateButton().attributes('disabled')).toBe('true');
});
});
@@ -93,7 +96,7 @@ describe('Ci variable modal', () => {
createComponent({ props: { selectedVariable: mockVariables[0] } });
});
- it('shows the submit button as enabled ', () => {
+ it('shows the submit button as enabled', () => {
expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined();
});
});
@@ -284,6 +287,21 @@ describe('Ci variable modal', () => {
});
});
+ describe('variable type dropdown', () => {
+ describe('default behaviour', () => {
+ beforeEach(() => {
+ createComponent({ mountFn: mountExtended });
+ });
+
+ it('adds each option as a dropdown item', () => {
+ expect(findVariableTypeDropdown().findAll('option')).toHaveLength(variableOptions.length);
+ variableOptions.forEach((v) => {
+ expect(findVariableTypeDropdown().text()).toContain(v.text);
+ });
+ });
+ });
+ });
+
describe('Validations', () => {
const maskError = 'This variable can not be masked.';
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 b43153d3d7c..4d0c378d10e 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
@@ -18,7 +18,7 @@ describe('Ci Variable Popover', () => {
});
};
- const findButton = () => wrapper.find(GlButton);
+ const findButton = () => wrapper.findComponent(GlButton);
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js
index 6681ab91a4a..b607232907b 100644
--- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js
+++ b/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js
@@ -40,12 +40,12 @@ describe('Ci variable modal', () => {
});
};
- const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
- const findModal = () => wrapper.find(ModalStub);
+ const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown);
+ const findModal = () => wrapper.findComponent(ModalStub);
const findAddorUpdateButton = () => findModal().find('[data-testid="ciUpdateOrAddVariableBtn"]');
const deleteVariableButton = () =>
findModal()
- .findAll(GlButton)
+ .findAllComponents(GlButton)
.wrappers.find((button) => button.props('variant') === 'danger');
afterEach(() => {
@@ -213,7 +213,7 @@ describe('Ci variable modal', () => {
const environmentScopeInput = wrapper
.find('[data-testid="environment-scope"]')
- .find(GlFormInput);
+ .findComponent(GlFormInput);
expect(findCiEnvironmentsDropdown().exists()).toBe(false);
expect(environmentScopeInput.attributes('readonly')).toBe('readonly');
});
diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js
index 89ba77858dc..6d633c8b740 100644
--- a/spec/frontend/ci_variable_list/mocks.js
+++ b/spec/frontend/ci_variable_list/mocks.js
@@ -1,4 +1,9 @@
-import { variableTypes, groupString, instanceString } from '~/ci_variable_list/constants';
+import {
+ variableTypes,
+ groupString,
+ instanceString,
+ projectString,
+} from '~/ci_variable_list/constants';
export const devName = 'dev';
export const prodName = 'prod';
@@ -11,8 +16,8 @@ export const mockVariables = (kind) => {
key: 'my-var',
masked: false,
protected: true,
- value: 'env_val',
- variableType: variableTypes.variableType,
+ value: 'variable_value',
+ variableType: variableTypes.envType,
},
{
__typename: `Ci${kind}Variable`,
@@ -20,7 +25,7 @@ export const mockVariables = (kind) => {
key: 'secret',
masked: true,
protected: false,
- value: 'the_secret_value',
+ value: 'another_value',
variableType: variableTypes.fileType,
},
];
@@ -77,7 +82,7 @@ export const mockProjectVariables = {
project: {
__typename: 'Project',
id: 1,
- ciVariables: createDefaultVars(),
+ ciVariables: createDefaultVars({ kind: projectString }),
},
},
};
diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js
index ae750ff426d..c7d07ead09b 100644
--- a/spec/frontend/ci_variable_list/store/mutations_spec.js
+++ b/spec/frontend/ci_variable_list/store/mutations_spec.js
@@ -36,7 +36,7 @@ describe('CI variable list mutations', () => {
});
describe('CLEAR_MODAL', () => {
- it('should clear modal state ', () => {
+ it('should clear modal state', () => {
const modalState = {
variable_type: 'Variable',
key: '',
diff --git a/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js b/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js
new file mode 100644
index 00000000000..2af64191a88
--- /dev/null
+++ b/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js
@@ -0,0 +1,96 @@
+import { GlLink, GlIcon, GlBadge } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import AgentIntegrationStatusRow from '~/clusters/agents/components/agent_integration_status_row.vue';
+
+const defaultProps = {
+ text: 'Default integration status',
+};
+
+describe('IntegrationStatus', () => {
+ let wrapper;
+
+ const createWrapper = ({ props = {}, glFeatures = {} } = {}) => {
+ wrapper = shallowMount(AgentIntegrationStatusRow, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ provide: {
+ glFeatures,
+ },
+ });
+ };
+
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findIcon = () => wrapper.findComponent(GlIcon);
+ const findBadge = () => wrapper.findComponent(GlBadge);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('icon', () => {
+ const icon = 'status-success';
+ const iconClass = 'text-success-500';
+ it.each`
+ props | iconName | iconClassName
+ ${{ icon, iconClass }} | ${icon} | ${iconClass}
+ ${{ icon }} | ${icon} | ${'text-info'}
+ ${{ iconClass }} | ${'information'} | ${iconClass}
+ ${null} | ${'information'} | ${'text-info'}
+ `('displays correct icon when props are $props', ({ props, iconName, iconClassName }) => {
+ createWrapper({ props });
+
+ expect(findIcon().props('name')).toBe(iconName);
+ expect(findIcon().attributes('class')).toContain(iconClassName);
+ });
+ });
+
+ describe('helpUrl', () => {
+ it('displays a link with the correct help url when provided in props', () => {
+ const props = {
+ helpUrl: 'help-page-path',
+ };
+ createWrapper({ props });
+
+ expect(findLink().attributes('href')).toBe(props.helpUrl);
+ expect(findLink().text()).toBe(defaultProps.text);
+ });
+
+ it("displays the text without a link when it's not provided", () => {
+ createWrapper();
+
+ expect(findLink().exists()).toBe(false);
+ expect(wrapper.text()).toBe(defaultProps.text);
+ });
+ });
+
+ describe('badge', () => {
+ it('does not display premium feature badge when featureName is not provided', () => {
+ createWrapper();
+
+ expect(findBadge().exists()).toBe(false);
+ });
+
+ it('does not display premium feature badge when featureName is provided and is available for the project', () => {
+ const props = { featureName: 'feature' };
+ const glFeatures = { feature: true };
+ createWrapper({ props, glFeatures });
+
+ expect(findBadge().exists()).toBe(false);
+ });
+
+ it('displays premium feature badge when featureName is provided and is not available for the project', () => {
+ const props = { featureName: 'feature' };
+ const glFeatures = { feature: false };
+ createWrapper({ props, glFeatures });
+
+ expect(findBadge().props()).toMatchObject({
+ icon: 'license',
+ variant: 'tier',
+ size: 'md',
+ });
+ expect(findBadge().text()).toBe(wrapper.vm.$options.i18n.premiumTitle);
+ });
+ });
+});
diff --git a/spec/frontend/clusters/agents/components/integration_status_spec.js b/spec/frontend/clusters/agents/components/integration_status_spec.js
new file mode 100644
index 00000000000..36f0e622452
--- /dev/null
+++ b/spec/frontend/clusters/agents/components/integration_status_spec.js
@@ -0,0 +1,111 @@
+import { GlCollapse, GlButton, GlIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import IntegrationStatus from '~/clusters/agents/components/integration_status.vue';
+import AgentIntegrationStatusRow from '~/clusters/agents/components/agent_integration_status_row.vue';
+import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
+import {
+ INTEGRATION_STATUS_VALID_TOKEN,
+ INTEGRATION_STATUS_NO_TOKEN,
+ INTEGRATION_STATUS_RESTRICTED_CI_CD,
+} from '~/clusters/agents/constants';
+
+const connectedTimeNow = new Date();
+const connectedTimeInactive = new Date(connectedTimeNow.getTime() - ACTIVE_CONNECTION_TIME);
+
+describe('IntegrationStatus', () => {
+ let wrapper;
+
+ const createWrapper = (tokens = []) => {
+ wrapper = shallowMountExtended(IntegrationStatus, {
+ propsData: { tokens },
+ });
+ };
+
+ const findCollapseButton = () => wrapper.findComponent(GlButton);
+ const findCollapse = () => wrapper.findComponent(GlCollapse);
+ const findStatusIcon = () => wrapper.findComponent(GlIcon);
+ const findAgentStatus = () => wrapper.findByTestId('agent-status');
+ const findAgentIntegrationStatusRows = () => wrapper.findAllComponents(AgentIntegrationStatusRow);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each`
+ lastUsedAt | status | iconName
+ ${null} | ${'Never connected'} | ${'status-neutral'}
+ ${connectedTimeNow} | ${'Connected'} | ${'status-success'}
+ ${connectedTimeInactive} | ${'Not connected'} | ${'status-alert'}
+ `(
+ 'displays correct text and icon when agent connection status is "$status"',
+ ({ lastUsedAt, status, iconName }) => {
+ const tokens = [{ lastUsedAt }];
+ createWrapper(tokens);
+
+ expect(findStatusIcon().props('name')).toBe(iconName);
+ expect(findAgentStatus().text()).toBe(status);
+ },
+ );
+
+ describe('default', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('shows the collapse toggle button', () => {
+ expect(findCollapseButton().text()).toBe(wrapper.vm.$options.i18n.title);
+ expect(findCollapseButton().attributes()).toMatchObject({
+ variant: 'link',
+ icon: 'chevron-right',
+ size: 'small',
+ });
+ });
+
+ it('sets collapse component as invisible by default', () => {
+ expect(findCollapse().props('visible')).toBeUndefined();
+ });
+ });
+
+ describe('when user clicks collapse toggle', () => {
+ beforeEach(() => {
+ createWrapper();
+ findCollapseButton().vm.$emit('click');
+ });
+
+ it('changes the collapse button icon', () => {
+ expect(findCollapseButton().props('icon')).toBe('chevron-down');
+ });
+
+ it('sets collapse component as visible', () => {
+ expect(findCollapse().attributes('visible')).toBe('true');
+ });
+ });
+
+ describe('integration status details', () => {
+ it.each`
+ agentStatus | tokens | integrationStatuses
+ ${'active'} | ${[{ lastUsedAt: connectedTimeNow }]} | ${[INTEGRATION_STATUS_VALID_TOKEN, INTEGRATION_STATUS_RESTRICTED_CI_CD]}
+ ${'inactive'} | ${[{ lastUsedAt: connectedTimeInactive }]} | ${[INTEGRATION_STATUS_RESTRICTED_CI_CD]}
+ ${'inactive'} | ${[]} | ${[INTEGRATION_STATUS_NO_TOKEN, INTEGRATION_STATUS_RESTRICTED_CI_CD]}
+ ${'unused'} | ${[{ lastUsedAt: null }]} | ${[INTEGRATION_STATUS_RESTRICTED_CI_CD]}
+ ${'unused'} | ${[]} | ${[INTEGRATION_STATUS_NO_TOKEN, INTEGRATION_STATUS_RESTRICTED_CI_CD]}
+ `(
+ 'displays AgentIntegrationStatusRow component with correct properties when agent status is $agentStatus and agent has $tokens.length tokens',
+ ({ tokens, integrationStatuses }) => {
+ createWrapper(tokens);
+
+ expect(findAgentIntegrationStatusRows().length).toBe(integrationStatuses.length);
+
+ integrationStatuses.forEach((integrationStatus, index) => {
+ expect(findAgentIntegrationStatusRows().at(index).props()).toMatchObject({
+ icon: integrationStatus.icon,
+ iconClass: integrationStatus.iconClass,
+ text: integrationStatus.text,
+ helpUrl: integrationStatus.helpUrl || null,
+ featureName: integrationStatus.featureName || null,
+ });
+ });
+ },
+ );
+ });
+});
diff --git a/spec/frontend/clusters/agents/components/show_spec.js b/spec/frontend/clusters/agents/components/show_spec.js
index f2f073544e3..efa85136b17 100644
--- a/spec/frontend/clusters/agents/components/show_spec.js
+++ b/spec/frontend/clusters/agents/components/show_spec.js
@@ -6,6 +6,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ClusterAgentShow from '~/clusters/agents/components/show.vue';
import TokenTable from '~/clusters/agents/components/token_table.vue';
import ActivityEvents from '~/clusters/agents/components/activity_events_list.vue';
+import IntegrationStatus from '~/clusters/agents/components/integration_status.vue';
import getAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql';
import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -76,6 +77,7 @@ describe('ClusterAgentShow', () => {
const findTokenCount = () => wrapper.findByTestId('cluster-agent-token-count').text();
const findEESecurityTabSlot = () => wrapper.findByTestId('ee-security-tab');
const findActivity = () => wrapper.findComponent(ActivityEvents);
+ const findIntegrationStatus = () => wrapper.findComponent(IntegrationStatus);
afterEach(() => {
wrapper.destroy();
@@ -107,6 +109,10 @@ describe('ClusterAgentShow', () => {
expect(findCreatedText()).toMatchInterpolatedText('Created by user-1 2 days ago');
});
+ it('displays agent integration status section', () => {
+ expect(findIntegrationStatus().exists()).toBe(true);
+ });
+
it('displays token count', () => {
expect(findTokenCount()).toMatchInterpolatedText(
`${ClusterAgentShow.i18n.tokens} ${defaultClusterAgent.tokens.count}`,
diff --git a/spec/frontend/clusters_list/components/agent_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js
index b78f0a3686c..9cbb83eedd2 100644
--- a/spec/frontend/clusters_list/components/agent_table_spec.js
+++ b/spec/frontend/clusters_list/components/agent_table_spec.js
@@ -36,7 +36,7 @@ describe('AgentTable', () => {
const findAgentLink = (at) => wrapper.findAllByTestId('cluster-agent-name-link').at(at);
const findStatusText = (at) => wrapper.findAllByTestId('cluster-agent-connection-status').at(at);
- const findStatusIcon = (at) => findStatusText(at).find(GlIcon);
+ const findStatusIcon = (at) => findStatusText(at).findComponent(GlIcon);
const findLastContactText = (at) => wrapper.findAllByTestId('cluster-agent-last-contact').at(at);
const findVersionText = (at) => wrapper.findAllByTestId('cluster-agent-version').at(at);
const findConfiguration = (at) =>
@@ -113,7 +113,7 @@ describe('AgentTable', () => {
texts,
lineNumber,
}) => {
- const findIcon = () => findVersionText(lineNumber).find(GlIcon);
+ const findIcon = () => findVersionText(lineNumber).findComponent(GlIcon);
const findPopover = () => wrapper.findByTestId(`popover-${agent}`);
const versionWarning = versionMismatch || versionOutdated;
@@ -151,7 +151,7 @@ describe('AgentTable', () => {
`(
'displays config file path as "$agentPath" at line $lineNumber',
({ agentConfig, link, lineNumber }) => {
- const findLink = findConfiguration(lineNumber).find(GlLink);
+ const findLink = findConfiguration(lineNumber).findComponent(GlLink);
expect(findLink.attributes('href')).toBe(link);
expect(findConfiguration(lineNumber).text()).toBe(agentConfig);
diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js
index 92cfff7d490..bff1e573dbd 100644
--- a/spec/frontend/clusters_list/components/agents_spec.js
+++ b/spec/frontend/clusters_list/components/agents_spec.js
@@ -334,7 +334,7 @@ describe('Agents', () => {
});
it('displays a loading icon', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/clusters_list/components/ancestor_notice_spec.js b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
index a9f11e6fdf8..758f6586e1a 100644
--- a/spec/frontend/clusters_list/components/ancestor_notice_spec.js
+++ b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
@@ -46,7 +46,7 @@ describe('ClustersAncestorNotice', () => {
});
it('displays link', () => {
- expect(wrapper.find(GlLink).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLink).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
index 218463b9adf..6f23ed47d2a 100644
--- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
@@ -142,7 +142,7 @@ describe('ClustersMainViewComponent', () => {
createWrapper({ certificateBasedClustersEnabled: false });
});
- it('it displays only the Agent tab', () => {
+ it('displays only the Agent tab', () => {
expect(findAllTabs()).toHaveLength(1);
const agentTab = findGlTabAtIndex(0);
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index 5c7635c1617..a3f42c1f161 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -142,7 +142,7 @@ describe('Clusters', () => {
({ lineNumber, result }) => {
const statuses = findStatuses();
const status = statuses.at(lineNumber);
- expect(status.find(GlLoadingIcon).exists()).toBe(result);
+ expect(status.findComponent(GlLoadingIcon).exists()).toBe(result);
},
);
});
diff --git a/spec/frontend/clusters_list/components/install_agent_modal_spec.js b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
index 964dd005a27..10264d6a011 100644
--- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js
+++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
@@ -65,7 +65,7 @@ describe('InstallAgentModal', () => {
const findAgentInstructions = () => findModal().findComponent(AgentToken);
const findButtonByVariant = (variant) =>
findModal()
- .findAll(GlButton)
+ .findAllComponents(GlButton)
.wrappers.find((button) => button.props('variant') === variant);
const findActionButton = () => findButtonByVariant('confirm');
const findCancelButton = () => findButtonByVariant('default');
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 8187ab75c58..3211ba44eff 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
@@ -11,7 +11,7 @@ describe('NodeErrorHelpText', () => {
await nextTick();
};
- const findPopover = () => wrapper.find(GlPopover);
+ const findPopover = () => wrapper.findComponent(GlPopover);
afterEach(() => {
wrapper.destroy();
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index b85047dc816..b9be262efd0 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -63,7 +63,7 @@ describe('Code navigation app component', () => {
it('hides popover when no definition set', () => {
factory();
- expect(wrapper.find(Popover).exists()).toBe(false);
+ expect(wrapper.findComponent(Popover).exists()).toBe(false);
});
it('renders popover when definition set', () => {
@@ -73,7 +73,7 @@ describe('Code navigation app component', () => {
currentBlobPath: 'index.js',
});
- expect(wrapper.find(Popover).exists()).toBe(true);
+ expect(wrapper.findComponent(Popover).exists()).toBe(true);
});
it('calls showDefinition when clicking blob viewer', () => {
diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js
index c038c04a0f8..874263e046a 100644
--- a/spec/frontend/code_navigation/components/popover_spec.js
+++ b/spec/frontend/code_navigation/components/popover_spec.js
@@ -115,8 +115,8 @@ describe('Code navigation popover component', () => {
definitionPathPrefix: DEFINITION_PATH_PREFIX,
});
- expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(true);
- expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(false);
+ expect(wrapper.findComponent({ ref: 'code-output' }).exists()).toBe(true);
+ expect(wrapper.findComponent({ ref: 'doc-output' }).exists()).toBe(false);
});
});
@@ -128,8 +128,8 @@ describe('Code navigation popover component', () => {
definitionPathPrefix: DEFINITION_PATH_PREFIX,
});
- expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(false);
- expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(true);
+ expect(wrapper.findComponent({ ref: 'code-output' }).exists()).toBe(false);
+ expect(wrapper.findComponent({ ref: 'doc-output' }).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js
index b8448709f0b..700c912029c 100644
--- a/spec/frontend/code_navigation/utils/index_spec.js
+++ b/spec/frontend/code_navigation/utils/index_spec.js
@@ -17,7 +17,7 @@ describe('getCurrentHoverElement', () => {
value
${'test'}
${undefined}
- `('it returns cached current key', ({ value }) => {
+ `('returns cached current key', ({ value }) => {
if (value) {
cachedData.set('current', value);
}
@@ -52,7 +52,7 @@ describe('addInteractionClass', () => {
${1} | ${0} | ${0}
${1} | ${0} | ${0}
`(
- 'it sets code navigation attributes for line $line and character $char',
+ 'sets code navigation attributes for line $line and character $char',
({ line, char, index }) => {
addInteractionClass({ path: 'index.js', d: { start_line: line, start_char: char } });
diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
index b1c8ba48475..fddc767953a 100644
--- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
+++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
@@ -1,14 +1,24 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
+import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants';
import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql';
-import { mockPipelineStagesQueryResponse, mockStages } from './mock_data';
+import * as graphQlUtils from '~/pipelines/components/graph/utils';
+import {
+ mockDownstreamQueryResponse,
+ mockPipelineStagesQueryResponse,
+ mockStages,
+ mockUpstreamDownstreamQueryResponse,
+ mockUpstreamQueryResponse,
+} from './mock_data';
jest.mock('~/flash');
@@ -17,61 +27,219 @@ Vue.use(VueApollo);
describe('Commit box pipeline mini graph', () => {
let wrapper;
- const findMiniGraph = () => wrapper.findByTestId('commit-box-mini-graph');
- const findUpstream = () => wrapper.findByTestId('commit-box-mini-graph-upstream');
- const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream');
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+ const downstreamHandler = jest.fn().mockResolvedValue(mockDownstreamQueryResponse);
+ const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
const stagesHandler = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse);
+ const upstreamDownstreamHandler = jest
+ .fn()
+ .mockResolvedValue(mockUpstreamDownstreamQueryResponse);
+ const upstreamHandler = jest.fn().mockResolvedValue(mockUpstreamQueryResponse);
+ const advanceToNextFetch = () => {
+ jest.advanceTimersByTime(COMMIT_BOX_POLL_INTERVAL);
+ };
- const createComponent = ({ props = {} } = {}) => {
- const handlers = [
- [getLinkedPipelinesQuery, {}],
+ const fullPath = 'gitlab-org/gitlab';
+ const iid = '315';
+ const createMockApolloProvider = (handler = downstreamHandler) => {
+ const requestHandlers = [
+ [getLinkedPipelinesQuery, handler],
[getPipelineStagesQuery, stagesHandler],
];
+ return createMockApollo(requestHandlers);
+ };
+
+ const createComponent = (handler) => {
wrapper = extendedWrapper(
shallowMount(CommitBoxPipelineMiniGraph, {
propsData: {
stages: mockStages,
- ...props,
},
- apolloProvider: createMockApollo(handlers),
+ provide: {
+ fullPath,
+ iid,
+ dataMethod: 'graphql',
+ graphqlResourceEtag: '/api/graphql:pipelines/id/320',
+ },
+ apolloProvider: createMockApolloProvider(handler),
}),
);
-
- return waitForPromises();
};
afterEach(() => {
wrapper.destroy();
});
- describe('linked pipelines', () => {
+ describe('loading state', () => {
+ it('should display loading state when loading', () => {
+ createComponent();
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findPipelineMiniGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('loaded state', () => {
beforeEach(async () => {
await createComponent();
});
- it('should display the mini pipeine graph', () => {
- expect(findMiniGraph().exists()).toBe(true);
+ it('should not display loading state after the query is resolved', async () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ expect(findPipelineMiniGraph().exists()).toBe(true);
});
- it('should not display linked pipelines', () => {
- expect(findUpstream().exists()).toBe(false);
- expect(findDownstream().exists()).toBe(false);
+ it('should display the pipeline mini graph', () => {
+ expect(findPipelineMiniGraph().exists()).toBe(true);
});
});
- describe('when data is mismatched', () => {
- beforeEach(async () => {
- await createComponent({ props: { stages: [] } });
+ describe('load upstream/downstream', () => {
+ const samplePipeline = {
+ __typename: expect.any(String),
+ id: expect.any(String),
+ path: expect.any(String),
+ project: expect.any(Object),
+ detailedStatus: expect.any(Object),
+ };
+
+ it('formatted stages should be passed to the pipeline mini graph', async () => {
+ const stage = mockStages[0];
+ const expectedStages = [
+ {
+ name: stage.name,
+ status: {
+ __typename: 'DetailedStatus',
+ id: stage.status.id,
+ icon: stage.status.icon,
+ group: stage.status.group,
+ },
+ dropdown_path: stage.dropdown_path,
+ title: stage.title,
+ },
+ ];
+
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findPipelineMiniGraph().props('stages')).toEqual(expectedStages);
+ });
+
+ it('should render a downstream pipeline only', async () => {
+ createComponent(downstreamHandler);
+
+ await waitForPromises();
+
+ const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+ const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+
+ expect(downstreamPipelines).toEqual(expect.any(Array));
+ expect(upstreamPipeline).toEqual(null);
+ });
+
+ it('should pass the pipeline path prop for the counter badge', async () => {
+ createComponent(downstreamHandler);
+
+ await waitForPromises();
+
+ const expectedPath = mockDownstreamQueryResponse.data.project.pipeline.path;
+ const pipelinePath = findPipelineMiniGraph().props('pipelinePath');
+
+ expect(pipelinePath).toBe(expectedPath);
+ });
+
+ it('should render an upstream pipeline only', async () => {
+ createComponent(upstreamHandler);
+
+ await waitForPromises();
+
+ const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+ const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+
+ expect(upstreamPipeline).toEqual(samplePipeline);
+ expect(downstreamPipelines).toHaveLength(0);
});
- it('calls create flash with expected arguments', () => {
+ it('should render downstream and upstream pipelines', async () => {
+ createComponent(upstreamDownstreamHandler);
+
+ await waitForPromises();
+
+ const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+ const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+
+ expect(upstreamPipeline).toEqual(samplePipeline);
+ expect(downstreamPipelines).toEqual(expect.arrayContaining([samplePipeline]));
+ });
+ });
+
+ describe('error state', () => {
+ it('createFlash should show if there is an error fetching the data', async () => {
+ createComponent({ handler: failedHandler });
+
+ await waitForPromises();
+
expect(createFlash).toHaveBeenCalledWith({
- message: 'There was a problem handling the pipeline data.',
- captureError: true,
- error: new Error('Rest stages and graphQl stages must be the same length'),
+ message: 'There was a problem fetching linked pipelines.',
});
});
});
+
+ describe('polling', () => {
+ it('polling interval is set for linked pipelines', () => {
+ createComponent();
+
+ const expectedInterval = wrapper.vm.$apollo.queries.pipeline.options.pollInterval;
+
+ expect(expectedInterval).toBe(COMMIT_BOX_POLL_INTERVAL);
+ });
+
+ it('polling interval is set for pipeline stages', () => {
+ createComponent();
+
+ const expectedInterval = wrapper.vm.$apollo.queries.pipelineStages.options.pollInterval;
+
+ expect(expectedInterval).toBe(COMMIT_BOX_POLL_INTERVAL);
+ });
+
+ it('polls for stages and linked pipelines', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(stagesHandler).toHaveBeenCalledTimes(1);
+ expect(downstreamHandler).toHaveBeenCalledTimes(1);
+
+ advanceToNextFetch();
+ await waitForPromises();
+
+ expect(stagesHandler).toHaveBeenCalledTimes(2);
+ expect(downstreamHandler).toHaveBeenCalledTimes(2);
+
+ advanceToNextFetch();
+ await waitForPromises();
+
+ expect(stagesHandler).toHaveBeenCalledTimes(3);
+ expect(downstreamHandler).toHaveBeenCalledTimes(3);
+ });
+
+ it('toggles query polling with visibility check', async () => {
+ jest.spyOn(graphQlUtils, 'toggleQueryPollingByVisibility');
+
+ createComponent();
+
+ await waitForPromises();
+
+ expect(graphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith(
+ wrapper.vm.$apollo.queries.pipelineStages,
+ );
+ expect(graphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith(
+ wrapper.vm.$apollo.queries.pipeline,
+ );
+ });
+ });
});
diff --git a/spec/frontend/commit/commit_pipeline_status_component_spec.js b/spec/frontend/commit/commit_pipeline_status_component_spec.js
index 43db6db00c1..73720c1cc88 100644
--- a/spec/frontend/commit/commit_pipeline_status_component_spec.js
+++ b/spec/frontend/commit/commit_pipeline_status_component_spec.js
@@ -37,9 +37,9 @@ describe('Commit pipeline status component', () => {
});
};
- const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findLoader = () => wrapper.findComponent(GlLoadingIcon);
const findLink = () => wrapper.find('a');
- const findCiIcon = () => findLink().find(CiIcon);
+ const findCiIcon = () => findLink().findComponent(CiIcon);
afterEach(() => {
wrapper.destroy();
diff --git a/spec/frontend/commit/mock_data.js b/spec/frontend/commit/mock_data.js
index 8db162c07c2..aef137e6fa5 100644
--- a/spec/frontend/commit/mock_data.js
+++ b/spec/frontend/commit/mock_data.js
@@ -3,116 +3,21 @@ export const mockStages = [
name: 'build',
title: 'build: passed',
status: {
+ __typename: 'DetailedStatus',
+ id: 'success-409-409',
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
- details_path: '/root/ci-project/-/pipelines/611#build',
+ details_path: '/root/ci-project/-/pipelines/318#build',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
- path: '/root/ci-project/-/pipelines/611#build',
- dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=build',
- },
- {
- name: 'test',
- title: 'test: passed',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- tooltip: 'passed',
- has_details: true,
- details_path: '/root/ci-project/-/pipelines/611#test',
- illustration: null,
- favicon:
- '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
- },
- path: '/root/ci-project/-/pipelines/611#test',
- dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=test',
- },
- {
- name: 'test_two',
- title: 'test_two: passed',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- tooltip: 'passed',
- has_details: true,
- details_path: '/root/ci-project/-/pipelines/611#test_two',
- illustration: null,
- favicon:
- '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
- },
- path: '/root/ci-project/-/pipelines/611#test_two',
- dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=test_two',
- },
- {
- name: 'manual',
- title: 'manual: skipped',
- status: {
- icon: 'status_skipped',
- text: 'skipped',
- label: 'skipped',
- group: 'skipped',
- tooltip: 'skipped',
- has_details: true,
- details_path: '/root/ci-project/-/pipelines/611#manual',
- illustration: null,
- favicon:
- '/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png',
- action: {
- icon: 'play',
- title: 'Play all manual',
- path: '/root/ci-project/-/pipelines/611/stages/manual/play_manual',
- method: 'post',
- button_title: 'Play all manual',
- },
- },
- path: '/root/ci-project/-/pipelines/611#manual',
- dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=manual',
- },
- {
- name: 'deploy',
- title: 'deploy: passed',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- tooltip: 'passed',
- has_details: true,
- details_path: '/root/ci-project/-/pipelines/611#deploy',
- illustration: null,
- favicon:
- '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
- },
- path: '/root/ci-project/-/pipelines/611#deploy',
- dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=deploy',
- },
- {
- name: 'qa',
- title: 'qa: passed',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- tooltip: 'passed',
- has_details: true,
- details_path: '/root/ci-project/-/pipelines/611#qa',
- illustration: null,
- favicon:
- '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
- },
- path: '/root/ci-project/-/pipelines/611#qa',
- dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=qa',
+ path: '/root/ci-project/-/pipelines/318#build',
+ dropdown_path: '/root/ci-project/-/pipelines/318/stage.json?stage=build',
},
];
@@ -161,3 +66,109 @@ export const mockPipelineStatusResponse = {
},
},
};
+
+export const mockDownstreamQueryResponse = {
+ data: {
+ project: {
+ id: '1',
+ pipeline: {
+ path: '/root/ci-project/-/pipelines/790',
+ id: 'pipeline-1',
+ downstream: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Ci::Pipeline/612',
+ path: '/root/job-log-sections/-/pipelines/612',
+ project: { id: '1', name: 'job-log-sections', __typename: 'Project' },
+ detailedStatus: {
+ id: 'status-1',
+ group: 'success',
+ icon: 'status_success',
+ label: 'passed',
+ __typename: 'DetailedStatus',
+ },
+ __typename: 'Pipeline',
+ },
+ ],
+ __typename: 'PipelineConnection',
+ },
+ upstream: null,
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockUpstreamDownstreamQueryResponse = {
+ data: {
+ project: {
+ id: '1',
+ pipeline: {
+ id: 'pipeline-1',
+ path: '/root/ci-project/-/pipelines/790',
+ downstream: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Ci::Pipeline/612',
+ path: '/root/job-log-sections/-/pipelines/612',
+ project: { id: '1', name: 'job-log-sections', __typename: 'Project' },
+ detailedStatus: {
+ id: 'status-1',
+ group: 'success',
+ icon: 'status_success',
+ label: 'passed',
+ __typename: 'DetailedStatus',
+ },
+ __typename: 'Pipeline',
+ },
+ ],
+ __typename: 'PipelineConnection',
+ },
+ upstream: {
+ id: 'gid://gitlab/Ci::Pipeline/610',
+ path: '/root/trigger-downstream/-/pipelines/610',
+ project: { id: '1', name: 'trigger-downstream', __typename: 'Project' },
+ detailedStatus: {
+ id: 'status-1',
+ group: 'success',
+ icon: 'status_success',
+ label: 'passed',
+ __typename: 'DetailedStatus',
+ },
+ __typename: 'Pipeline',
+ },
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockUpstreamQueryResponse = {
+ data: {
+ project: {
+ id: '1',
+ pipeline: {
+ id: 'pipeline-1',
+ path: '/root/ci-project/-/pipelines/790',
+ downstream: {
+ nodes: [],
+ __typename: 'PipelineConnection',
+ },
+ upstream: {
+ id: 'gid://gitlab/Ci::Pipeline/610',
+ path: '/root/trigger-downstream/-/pipelines/610',
+ project: { id: '1', name: 'trigger-downstream', __typename: 'Project' },
+ detailedStatus: {
+ id: 'status-1',
+ group: 'success',
+ icon: 'status_success',
+ label: 'passed',
+ __typename: 'DetailedStatus',
+ },
+ __typename: 'Pipeline',
+ },
+ },
+ __typename: 'Project',
+ },
+ },
+};
diff --git a/spec/frontend/commit/pipelines/pipelines_table_spec.js b/spec/frontend/commit/pipelines/pipelines_table_spec.js
index 71ee12cf02d..d89a238105b 100644
--- a/spec/frontend/commit/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js
@@ -302,6 +302,33 @@ describe('Pipelines table in Commits and Merge requests', () => {
expect(findModal()).not.toBeNull();
});
});
+
+ describe('when no pipelines were created on a forked merge request', () => {
+ beforeEach(async () => {
+ mock.onGet('endpoint.json').reply(200, []);
+
+ createComponent({
+ projectId: '5',
+ mergeRequestId: 3,
+ canCreatePipelineInTargetProject: true,
+ sourceProjectFullPath: 'test/parent-project',
+ targetProjectFullPath: 'test/fork-project',
+ });
+
+ jest.spyOn(findModal().vm, 'show').mockReturnValue();
+
+ await waitForPromises();
+ });
+
+ it('should show security modal from empty state run pipeline button', () => {
+ expect(findEmptyState().exists()).toBe(true);
+ expect(findModal().exists()).toBe(true);
+
+ findRunPipelineBtn().trigger('click');
+
+ expect(findModal().vm.show).toHaveBeenCalled();
+ });
+ });
});
describe('unsuccessfull request', () => {
diff --git a/spec/frontend/confidential_merge_request/components/dropdown_spec.js b/spec/frontend/confidential_merge_request/components/dropdown_spec.js
index 14a0b98a0d5..770f2636648 100644
--- a/spec/frontend/confidential_merge_request/components/dropdown_spec.js
+++ b/spec/frontend/confidential_merge_request/components/dropdown_spec.js
@@ -30,18 +30,18 @@ describe('Confidential merge request project dropdown component', () => {
},
]);
- expect(vm.findAll(GlDropdownItem).length).toBe(2);
+ expect(vm.findAllComponents(GlDropdownItem).length).toBe(2);
});
it('shows lock icon', () => {
factory();
- expect(vm.find(GlDropdown).props('icon')).toBe('lock');
+ expect(vm.findComponent(GlDropdown).props('icon')).toBe('lock');
});
it('has dropdown text', () => {
factory();
- expect(vm.find(GlDropdown).props('text')).toBe('Select private project');
+ expect(vm.findComponent(GlDropdown).props('text')).toBe('Select private project');
});
});
diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
index b54f7cf17c8..6ad8a9de8d3 100644
--- a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
+++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
@@ -1,49 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`content_editor/components/toolbar_link_button renders dropdown component 1`] = `
-"<div class=\\"dropdown b-dropdown gl-new-dropdown btn-group\\" aria-label=\\"Insert link\\" title=\\"Insert link\\">
- <!----><button aria-haspopup=\\"true\\" aria-expanded=\\"false\\" type=\\"button\\" class=\\"btn dropdown-toggle btn-default btn-sm gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only\\">
- <!----> <svg data-testid=\\"link-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"dropdown-icon gl-icon s16\\">
- <use href=\\"#link\\"></use>
- </svg> <span class=\\"gl-new-dropdown-button-text\\"></span> <svg data-testid=\\"chevron-down-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"gl-button-icon dropdown-chevron gl-icon s16\\">
- <use href=\\"#chevron-down\\"></use>
- </svg></button>
- <ul role=\\"menu\\" tabindex=\\"-1\\" class=\\"dropdown-menu\\">
- <div class=\\"gl-new-dropdown-inner\\">
+"<div title=\\"Insert link\\" lazy=\\"\\">
+ <li role=\\"presentation\\" class=\\"gl-px-3!\\">
+ <form tabindex=\\"-1\\" class=\\"b-dropdown-form gl-p-0\\">
+ <div role=\\"group\\" class=\\"input-group\\" placeholder=\\"Link URL\\">
+ <!---->
+ <!----> <input type=\\"text\\" placeholder=\\"Link URL\\" class=\\"form-control gl-form-input\\">
+ <div class=\\"input-group-append\\"><button type=\\"button\\" class=\\"btn btn-confirm btn-md gl-button\\">
+ <!---->
+ <!----> <span class=\\"gl-button-text\\">Apply</span></button></div>
+ <!---->
+ </div>
+ </form>
+ </li>
+ <li role=\\"presentation\\" class=\\"gl-new-dropdown-divider\\">
+ <hr role=\\"separator\\" aria-orientation=\\"horizontal\\" class=\\"dropdown-divider\\">
+ </li>
+ <li role=\\"presentation\\" class=\\"gl-new-dropdown-item\\"><button role=\\"menuitem\\" type=\\"button\\" class=\\"dropdown-item\\">
+ <!---->
<!---->
<!---->
- <div class=\\"gl-new-dropdown-contents\\">
+ <div class=\\"gl-new-dropdown-item-text-wrapper\\">
+ <p class=\\"gl-new-dropdown-item-text-primary\\">
+ Upload file
+ </p>
<!---->
- <li role=\\"presentation\\" class=\\"gl-px-3!\\">
- <form tabindex=\\"-1\\" class=\\"b-dropdown-form gl-p-0\\">
- <div role=\\"group\\" class=\\"input-group\\" placeholder=\\"Link URL\\">
- <!---->
- <!----> <input type=\\"text\\" placeholder=\\"Link URL\\" class=\\"form-control gl-form-input\\">
- <div class=\\"input-group-append\\"><button type=\\"button\\" class=\\"btn btn-confirm btn-md gl-button\\">
- <!---->
- <!----> <span class=\\"gl-button-text\\">Apply</span></button></div>
- <!---->
- </div>
- </form>
- </li>
- <li role=\\"presentation\\" class=\\"gl-new-dropdown-divider\\">
- <hr role=\\"separator\\" aria-orientation=\\"horizontal\\" class=\\"dropdown-divider\\">
- </li>
- <li role=\\"presentation\\" class=\\"gl-new-dropdown-item\\"><button role=\\"menuitem\\" type=\\"button\\" class=\\"dropdown-item\\">
- <!---->
- <!---->
- <!---->
- <div class=\\"gl-new-dropdown-item-text-wrapper\\">
- <p class=\\"gl-new-dropdown-item-text-primary\\">
- Upload file
- </p>
- <!---->
- </div>
- <!---->
- </button></li> <input type=\\"file\\" name=\\"content_editor_attachment\\" class=\\"gl-display-none\\">
</div>
<!---->
- </div>
- </ul>
+ </button></li>
</div>"
`;
diff --git a/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
new file mode 100644
index 00000000000..0700cf5d529
--- /dev/null
+++ b/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
@@ -0,0 +1,126 @@
+import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import { createTestEditor } from '../../test_utils';
+
+jest.mock('@tiptap/extension-bubble-menu');
+
+describe('content_editor/components/bubble_menus/bubble_menu', () => {
+ let wrapper;
+ let tiptapEditor;
+ const pluginKey = 'key';
+ const shouldShow = jest.fn();
+ const tippyOptions = { placement: 'bottom' };
+ const pluginInitializationResult = {};
+
+ const buildEditor = () => {
+ tiptapEditor = createTestEditor();
+ };
+
+ const createWrapper = (propsData = {}) => {
+ wrapper = shallowMountExtended(BubbleMenu, {
+ provide: {
+ tiptapEditor,
+ },
+ propsData: {
+ pluginKey,
+ shouldShow,
+ tippyOptions,
+ ...propsData,
+ },
+ slots: {
+ default: '<div>menu content</div>',
+ },
+ });
+ };
+
+ const setupMocks = () => {
+ BubbleMenuPlugin.mockReturnValueOnce(pluginInitializationResult);
+ jest.spyOn(tiptapEditor, 'registerPlugin').mockImplementationOnce(() => true);
+ };
+
+ const invokeTippyEvent = (eventName, eventArgs) => {
+ const pluginConfig = BubbleMenuPlugin.mock.calls[0][0];
+
+ pluginConfig.tippyOptions[eventName](eventArgs);
+ };
+
+ beforeEach(() => {
+ buildEditor();
+ setupMocks();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('initializes BubbleMenuPlugin', async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ expect(BubbleMenuPlugin).toHaveBeenCalledWith({
+ pluginKey,
+ editor: tiptapEditor,
+ shouldShow,
+ element: wrapper.vm.$el,
+ tippyOptions: expect.objectContaining({
+ onHidden: expect.any(Function),
+ onShow: expect.any(Function),
+ ...tippyOptions,
+ }),
+ });
+
+ expect(tiptapEditor.registerPlugin).toHaveBeenCalledWith(pluginInitializationResult);
+ });
+
+ it('does not render default slot by default', async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ expect(wrapper.text()).not.toContain('menu content');
+ });
+
+ describe('when onShow event handler is invoked', () => {
+ const onShowArgs = {};
+
+ beforeEach(async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ invokeTippyEvent('onShow', onShowArgs);
+ });
+
+ it('displays the menu content', () => {
+ expect(wrapper.text()).toContain('menu content');
+ });
+
+ it('emits show event', () => {
+ expect(wrapper.emitted('show')).toEqual([[onShowArgs]]);
+ });
+ });
+
+ describe('when onHidden event handler is invoked', () => {
+ const onHiddenArgs = {};
+
+ beforeEach(async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ invokeTippyEvent('onShow', onHiddenArgs);
+ invokeTippyEvent('onHidden', onHiddenArgs);
+ });
+
+ it('displays the menu content', () => {
+ expect(wrapper.text()).not.toContain('menu content');
+ });
+
+ it('emits show event', () => {
+ expect(wrapper.emitted('hidden')).toEqual([[onHiddenArgs]]);
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
index 154035a46ed..378b11f4ae9 100644
--- a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
@@ -1,4 +1,3 @@
-import { BubbleMenu } from '@tiptap/vue-2';
import {
GlDropdown,
GlDropdownForm,
@@ -9,8 +8,9 @@ import {
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
-import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block.vue';
+import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block_bubble_menu.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
import Diagram from '~/content_editor/extensions/diagram';
import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader';
@@ -18,7 +18,7 @@ import { createTestEditor, emitEditorEvent } from '../../test_utils';
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
-describe('content_editor/components/bubble_menus/code_block', () => {
+describe('content_editor/components/bubble_menus/code_block_bubble_menu', () => {
let wrapper;
let tiptapEditor;
let contentEditor;
@@ -40,6 +40,7 @@ describe('content_editor/components/bubble_menus/code_block', () => {
},
stubs: {
GlDropdownItem: stubComponent(GlDropdownItem),
+ BubbleMenu: stubComponent(BubbleMenu),
},
});
};
@@ -73,7 +74,6 @@ describe('content_editor/components/bubble_menus/code_block', () => {
await emitEditorEvent({ event: 'transaction', tiptapEditor });
- expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
diff --git a/spec/frontend/content_editor/components/bubble_menus/formatting_spec.js b/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
index 1e2f58d9e40..cce17176129 100644
--- a/spec/frontend/content_editor/components/bubble_menus/formatting_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
@@ -1,7 +1,8 @@
-import { BubbleMenu } from '@tiptap/vue-2';
import { mockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import FormattingBubbleMenu from '~/content_editor/components/bubble_menus/formatting.vue';
+import FormattingBubbleMenu from '~/content_editor/components/bubble_menus/formatting_bubble_menu.vue';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import { stubComponent } from 'helpers/stub_component';
import {
BUBBLE_MENU_TRACKING_ACTION,
@@ -9,7 +10,7 @@ import {
} from '~/content_editor/constants';
import { createTestEditor } from '../../test_utils';
-describe('content_editor/components/bubble_menus/formatting', () => {
+describe('content_editor/components/bubble_menus/formatting_bubble_menu', () => {
let wrapper;
let trackingSpy;
let tiptapEditor;
@@ -25,6 +26,9 @@ describe('content_editor/components/bubble_menus/formatting', () => {
provide: {
tiptapEditor,
},
+ stubs: {
+ BubbleMenu: stubComponent(BubbleMenu),
+ },
});
};
@@ -41,7 +45,6 @@ describe('content_editor/components/bubble_menus/formatting', () => {
buildWrapper();
const bubbleMenu = wrapper.findComponent(BubbleMenu);
- expect(bubbleMenu.props().editor).toBe(tiptapEditor);
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
diff --git a/spec/frontend/content_editor/components/bubble_menus/link_spec.js b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
index 93204deb68c..9aa9c6483f4 100644
--- a/spec/frontend/content_editor/components/bubble_menus/link_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
@@ -1,18 +1,20 @@
import { GlLink, GlForm } from '@gitlab/ui';
-import { BubbleMenu } from '@tiptap/vue-2';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import LinkBubbleMenu from '~/content_editor/components/bubble_menus/link.vue';
+import LinkBubbleMenu from '~/content_editor/components/bubble_menus/link_bubble_menu.vue';
+import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import { stubComponent } from 'helpers/stub_component';
import Link from '~/content_editor/extensions/link';
-import { createTestEditor, emitEditorEvent } from '../../test_utils';
+import { createTestEditor } from '../../test_utils';
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
-describe('content_editor/components/bubble_menus/link', () => {
+describe('content_editor/components/bubble_menus/link_bubble_menu', () => {
let wrapper;
let tiptapEditor;
let contentEditor;
- let bubbleMenu;
let eventHub;
const buildEditor = () => {
@@ -28,9 +30,28 @@ describe('content_editor/components/bubble_menus/link', () => {
contentEditor,
eventHub,
},
+ stubs: {
+ BubbleMenu: stubComponent(BubbleMenu),
+ },
});
};
+ const showMenu = () => {
+ wrapper.findComponent(BubbleMenu).vm.$emit('show');
+ return nextTick();
+ };
+
+ const buildWrapperAndDisplayMenu = () => {
+ buildWrapper();
+
+ return showMenu();
+ };
+
+ const findBubbleMenu = () => wrapper.findComponent(BubbleMenu);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findEditorStateObserver = () => wrapper.findComponent(EditorStateObserver);
+ const findEditLinkButton = () => wrapper.findByTestId('edit-link');
+
const expectLinkButtonsToExist = (exist = true) => {
expect(wrapper.findComponent(GlLink).exists()).toBe(exist);
expect(wrapper.findByTestId('copy-link-url').exists()).toBe(exist);
@@ -40,7 +61,6 @@ describe('content_editor/components/bubble_menus/link', () => {
beforeEach(async () => {
buildEditor();
- buildWrapper();
tiptapEditor
.chain()
@@ -49,10 +69,6 @@ describe('content_editor/components/bubble_menus/link', () => {
)
.setTextSelection(14) // put cursor in the middle of the link
.run();
-
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
-
- bubbleMenu = wrapper.findComponent(BubbleMenu);
});
afterEach(() => {
@@ -60,13 +76,15 @@ describe('content_editor/components/bubble_menus/link', () => {
});
it('renders bubble menu component', async () => {
- expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
- expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
+ await buildWrapperAndDisplayMenu();
+
+ expect(findBubbleMenu().classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
it('shows a clickable link to the URL in the link node', async () => {
- const link = wrapper.findComponent(GlLink);
- expect(link.attributes()).toEqual(
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
expect.objectContaining({
href: '/path/to/project/-/wikis/uploads/my_file.pdf',
'aria-label': 'uploads/my_file.pdf',
@@ -74,11 +92,82 @@ describe('content_editor/components/bubble_menus/link', () => {
target: '_blank',
}),
);
- expect(link.text()).toBe('uploads/my_file.pdf');
+ expect(findLink().text()).toBe('uploads/my_file.pdf');
+ });
+
+ it('updates the bubble menu state when @selectionUpdate event is triggered', async () => {
+ const linkUrl = 'https://gitlab.com';
+
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '/path/to/project/-/wikis/uploads/my_file.pdf',
+ }),
+ );
+
+ tiptapEditor
+ .chain()
+ .setContent(
+ `Link to <a href="${linkUrl}" data-canonical-src="${linkUrl}" title="Click here to download">GitLab</a>`,
+ )
+ .setTextSelection(11)
+ .run();
+
+ findEditorStateObserver().vm.$emit('selectionUpdate');
+
+ await nextTick();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: linkUrl,
+ }),
+ );
+ });
+
+ describe('when the selection changes within the same link', () => {
+ it('does not update the bubble menu state', async () => {
+ await buildWrapperAndDisplayMenu();
+
+ await findEditLinkButton().trigger('click');
+
+ expect(wrapper.findComponent(GlForm).exists()).toBe(true);
+
+ tiptapEditor.commands.setTextSelection(13);
+
+ findEditorStateObserver().vm.$emit('selectionUpdate');
+
+ await nextTick();
+
+ expect(wrapper.findComponent(GlForm).exists()).toBe(true);
+ });
+ });
+
+ it('cleans bubble menu state when hidden event is triggered', async () => {
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '/path/to/project/-/wikis/uploads/my_file.pdf',
+ }),
+ );
+
+ findBubbleMenu().vm.$emit('hidden');
+
+ await nextTick();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '#',
+ }),
+ );
+ expect(findLink().text()).toEqual('');
});
describe('copy button', () => {
it('copies the canonical link to clipboard', async () => {
+ await buildWrapperAndDisplayMenu();
+
jest.spyOn(navigator.clipboard, 'writeText');
await wrapper.findByTestId('copy-link-url').vm.$emit('click');
@@ -89,6 +178,7 @@ describe('content_editor/components/bubble_menus/link', () => {
describe('remove link button', () => {
it('removes the link', async () => {
+ await buildWrapperAndDisplayMenu();
await wrapper.findByTestId('remove-link').vm.$emit('click');
expect(tiptapEditor.getHTML()).toBe('<p>Download PDF File</p>');
@@ -106,7 +196,7 @@ describe('content_editor/components/bubble_menus/link', () => {
.setTextSelection(4)
.run();
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ await buildWrapperAndDisplayMenu();
});
it('directly opens the edit form for a placeholder link', async () => {
@@ -133,6 +223,7 @@ describe('content_editor/components/bubble_menus/link', () => {
let linkTitleInput;
beforeEach(async () => {
+ await buildWrapperAndDisplayMenu();
await wrapper.findByTestId('edit-link').vm.$emit('click');
linkHrefInput = wrapper.findByTestId('link-href');
@@ -157,19 +248,6 @@ describe('content_editor/components/bubble_menus/link', () => {
expect(to).toBe(18);
});
- it('shows the copy/edit/remove link buttons again if selection changes to another non-link and then back again to a link', async () => {
- expectLinkButtonsToExist(false);
-
- tiptapEditor.commands.setTextSelection(3);
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
-
- tiptapEditor.commands.setTextSelection(14);
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
-
- expectLinkButtonsToExist(true);
- expect(wrapper.findComponent(GlForm).exists()).toBe(false);
- });
-
describe('after making changes in the form and clicking apply', () => {
beforeEach(async () => {
linkHrefInput.setValue('https://google.com');
diff --git a/spec/frontend/content_editor/components/bubble_menus/media_spec.js b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
index fada4f06743..13c6495ac41 100644
--- a/spec/frontend/content_editor/components/bubble_menus/media_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
@@ -1,7 +1,8 @@
import { GlLink, GlForm } from '@gitlab/ui';
-import { BubbleMenu } from '@tiptap/vue-2';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media.vue';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media_bubble_menu.vue';
+import { stubComponent } from 'helpers/stub_component';
import eventHubFactory from '~/helpers/event_hub_factory';
import Image from '~/content_editor/extensions/image';
import Audio from '~/content_editor/extensions/audio';
@@ -33,7 +34,7 @@ describe.each`
${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${'test-file.mp3'} | ${TIPTAP_AUDIO_HTML}
${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${'test-file.mp4'} | ${TIPTAP_VIDEO_HTML}
`(
- 'content_editor/components/bubble_menus/media ($mediaType)',
+ 'content_editor/components/bubble_menus/media_bubble_menu ($mediaType)',
({ mediaType, mediaHTML, filePath, mediaOutputHTML }) => {
let wrapper;
let tiptapEditor;
@@ -54,11 +55,14 @@ describe.each`
contentEditor,
eventHub,
},
+ stubs: {
+ BubbleMenu: stubComponent(BubbleMenu),
+ },
});
};
const selectFile = async (file) => {
- const input = wrapper.find({ ref: 'fileSelector' });
+ const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });
@@ -94,7 +98,6 @@ describe.each`
});
it('renders bubble menu component', async () => {
- expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
diff --git a/spec/frontend/content_editor/components/content_editor_alert_spec.js b/spec/frontend/content_editor/components/content_editor_alert_spec.js
index 12484cb13c6..ee9ead8f8a7 100644
--- a/spec/frontend/content_editor/components/content_editor_alert_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_alert_spec.js
@@ -51,6 +51,16 @@ describe('content_editor/components/content_editor_alert', () => {
},
);
+ it('does not show primary action by default', async () => {
+ const message = 'error message';
+
+ createWrapper();
+ eventHub.$emit(ALERT_EVENT, { message });
+ await nextTick();
+
+ expect(findErrorAlert().attributes().primaryButtonText).toBeUndefined();
+ });
+
it('allows dismissing the error', async () => {
const message = 'error message';
@@ -62,4 +72,19 @@ describe('content_editor/components/content_editor_alert', () => {
expect(findErrorAlert().exists()).toBe(false);
});
+
+ it('allows dismissing the error with a primary action button', async () => {
+ const message = 'error message';
+ const actionLabel = 'Retry';
+ const action = jest.fn();
+
+ createWrapper();
+ eventHub.$emit(ALERT_EVENT, { message, action, actionLabel });
+ await nextTick();
+ findErrorAlert().vm.$emit('primaryAction');
+ await nextTick();
+
+ expect(action).toHaveBeenCalled();
+ expect(findErrorAlert().exists()).toBe(false);
+ });
});
diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js
index 0ba2672100b..ae52cb05eaf 100644
--- a/spec/frontend/content_editor/components/content_editor_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_spec.js
@@ -1,136 +1,227 @@
-import { EditorContent } from '@tiptap/vue-2';
+import { GlAlert } from '@gitlab/ui';
+import { EditorContent, Editor } from '@tiptap/vue-2';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ContentEditor from '~/content_editor/components/content_editor.vue';
import ContentEditorAlert from '~/content_editor/components/content_editor_alert.vue';
import ContentEditorProvider from '~/content_editor/components/content_editor_provider.vue';
import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue';
-import FormattingBubbleMenu from '~/content_editor/components/bubble_menus/formatting.vue';
+import FormattingBubbleMenu from '~/content_editor/components/bubble_menus/formatting_bubble_menu.vue';
+import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block_bubble_menu.vue';
+import LinkBubbleMenu from '~/content_editor/components/bubble_menus/link_bubble_menu.vue';
+import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media_bubble_menu.vue';
import TopToolbar from '~/content_editor/components/top_toolbar.vue';
import LoadingIndicator from '~/content_editor/components/loading_indicator.vue';
-import { emitEditorEvent } from '../test_utils';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/emoji');
describe('ContentEditor', () => {
let wrapper;
- let contentEditor;
let renderMarkdown;
const uploadsPath = '/uploads';
const findEditorElement = () => wrapper.findByTestId('content-editor');
const findEditorContent = () => wrapper.findComponent(EditorContent);
const findEditorStateObserver = () => wrapper.findComponent(EditorStateObserver);
- const createWrapper = (propsData = {}) => {
- renderMarkdown = jest.fn();
-
+ const findLoadingIndicator = () => wrapper.findComponent(LoadingIndicator);
+ const findContentEditorAlert = () => wrapper.findComponent(ContentEditorAlert);
+ const createWrapper = ({ markdown } = {}) => {
wrapper = shallowMountExtended(ContentEditor, {
propsData: {
renderMarkdown,
uploadsPath,
- ...propsData,
+ markdown,
},
stubs: {
EditorStateObserver,
ContentEditorProvider,
- },
- listeners: {
- initialized(editor) {
- contentEditor = editor;
- },
+ ContentEditorAlert,
},
});
};
+ beforeEach(() => {
+ renderMarkdown = jest.fn();
+ });
+
afterEach(() => {
wrapper.destroy();
});
- it('triggers initialized event and provides contentEditor instance as event data', () => {
+ it('triggers initialized event', () => {
createWrapper();
- expect(contentEditor).not.toBeFalsy();
+ expect(wrapper.emitted('initialized')).toHaveLength(1);
});
- it('renders EditorContent component and provides tiptapEditor instance', () => {
- createWrapper();
+ it('renders EditorContent component and provides tiptapEditor instance', async () => {
+ const markdown = 'hello world';
+
+ createWrapper({ markdown });
+
+ renderMarkdown.mockResolvedValueOnce(markdown);
+
+ await nextTick();
const editorContent = findEditorContent();
- expect(editorContent.props().editor).toBe(contentEditor.tiptapEditor);
+ expect(editorContent.props().editor).toBeInstanceOf(Editor);
expect(editorContent.classes()).toContain('md');
});
- it('renders ContentEditorProvider component', () => {
- createWrapper();
+ it('renders ContentEditorProvider component', async () => {
+ await createWrapper();
expect(wrapper.findComponent(ContentEditorProvider).exists()).toBe(true);
});
- it('renders top toolbar component', () => {
- createWrapper();
+ it('renders top toolbar component', async () => {
+ await createWrapper();
expect(wrapper.findComponent(TopToolbar).exists()).toBe(true);
});
- it('adds is-focused class when focus event is emitted', async () => {
- createWrapper();
+ describe('when setting initial content', () => {
+ it('displays loading indicator', async () => {
+ createWrapper();
- await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'focus' });
+ await nextTick();
- expect(findEditorElement().classes()).toContain('is-focused');
- });
+ expect(findLoadingIndicator().exists()).toBe(true);
+ });
- it('removes is-focused class when blur event is emitted', async () => {
- createWrapper();
+ it('emits loading event', async () => {
+ createWrapper();
- await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'focus' });
- await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'blur' });
+ await nextTick();
- expect(findEditorElement().classes()).not.toContain('is-focused');
- });
+ expect(wrapper.emitted('loading')).toHaveLength(1);
+ });
- it('emits change event when document is updated', async () => {
- createWrapper();
+ describe('succeeds', () => {
+ beforeEach(async () => {
+ renderMarkdown.mockResolvedValueOnce('hello world');
- await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'update' });
+ createWrapper({ markddown: 'hello world' });
+ await nextTick();
+ });
- expect(wrapper.emitted('change')).toEqual([
- [
- {
- empty: contentEditor.empty,
- },
- ],
- ]);
- });
+ it('hides loading indicator', async () => {
+ await nextTick();
+ expect(findLoadingIndicator().exists()).toBe(false);
+ });
- it('renders content_editor_alert component', () => {
- createWrapper();
+ it('emits loadingSuccess event', () => {
+ expect(wrapper.emitted('loadingSuccess')).toHaveLength(1);
+ });
+ });
+
+ describe('fails', () => {
+ beforeEach(async () => {
+ renderMarkdown.mockRejectedValueOnce(new Error());
+
+ createWrapper({ markddown: 'hello world' });
+ await nextTick();
+ });
+
+ it('sets the content editor as read only when loading content fails', async () => {
+ await nextTick();
- expect(wrapper.findComponent(ContentEditorAlert).exists()).toBe(true);
+ expect(findEditorContent().props().editor.isEditable).toBe(false);
+ });
+
+ it('hides loading indicator', async () => {
+ await nextTick();
+
+ expect(findLoadingIndicator().exists()).toBe(false);
+ });
+
+ it('emits loadingError event', () => {
+ expect(wrapper.emitted('loadingError')).toHaveLength(1);
+ });
+
+ it('displays error alert indicating that the content editor failed to load', () => {
+ expect(findContentEditorAlert().text()).toContain(
+ 'An error occurred while trying to render the content editor. Please try again.',
+ );
+ });
+
+ describe('when clicking the retry button in the loading error alert and loading succeeds', () => {
+ beforeEach(async () => {
+ renderMarkdown.mockResolvedValueOnce('hello markdown');
+ await wrapper.findComponent(GlAlert).vm.$emit('primaryAction');
+ });
+
+ it('hides the loading error alert', () => {
+ expect(findContentEditorAlert().text()).toBe('');
+ });
+
+ it('sets the content editor as writable', async () => {
+ await nextTick();
+
+ expect(findEditorContent().props().editor.isEditable).toBe(true);
+ });
+ });
+ });
});
- it('renders loading indicator component', () => {
- createWrapper();
+ describe('when focused event is emitted', () => {
+ beforeEach(async () => {
+ createWrapper();
+
+ findEditorStateObserver().vm.$emit('focus');
+
+ await nextTick();
+ });
- expect(wrapper.findComponent(LoadingIndicator).exists()).toBe(true);
+ it('adds is-focused class when focus event is emitted', () => {
+ expect(findEditorElement().classes()).toContain('is-focused');
+ });
+
+ it('removes is-focused class when blur event is emitted', async () => {
+ findEditorStateObserver().vm.$emit('blur');
+
+ await nextTick();
+
+ expect(findEditorElement().classes()).not.toContain('is-focused');
+ });
});
- it('renders formatting bubble menu', () => {
- createWrapper();
+ describe('when editorStateObserver emits docUpdate event', () => {
+ it('emits change event with the latest markdown', async () => {
+ const markdown = 'Loaded content';
- expect(wrapper.findComponent(FormattingBubbleMenu).exists()).toBe(true);
+ renderMarkdown.mockResolvedValueOnce(markdown);
+
+ createWrapper({ markdown: 'initial content' });
+
+ await nextTick();
+ await waitForPromises();
+
+ findEditorStateObserver().vm.$emit('docUpdate');
+
+ expect(wrapper.emitted('change')).toEqual([
+ [
+ {
+ markdown,
+ changed: false,
+ empty: false,
+ },
+ ],
+ ]);
+ });
});
it.each`
- event
- ${'loading'}
- ${'loadingSuccess'}
- ${'loadingError'}
- `('broadcasts $event event triggered by editor-state-observer component', ({ event }) => {
+ name | component
+ ${'formatting'} | ${FormattingBubbleMenu}
+ ${'link'} | ${LinkBubbleMenu}
+ ${'media'} | ${MediaBubbleMenu}
+ ${'codeBlock'} | ${CodeBlockBubbleMenu}
+ `('renders formatting bubble menu', ({ component }) => {
createWrapper();
- findEditorStateObserver().vm.$emit(event);
-
- expect(wrapper.emitted(event)).toHaveLength(1);
+ expect(wrapper.findComponent(component).exists()).toBe(true);
});
});
diff --git a/spec/frontend/content_editor/components/editor_state_observer_spec.js b/spec/frontend/content_editor/components/editor_state_observer_spec.js
index 51a594a606b..e8c2d8c8793 100644
--- a/spec/frontend/content_editor/components/editor_state_observer_spec.js
+++ b/spec/frontend/content_editor/components/editor_state_observer_spec.js
@@ -4,12 +4,7 @@ import EditorStateObserver, {
tiptapToComponentMap,
} from '~/content_editor/components/editor_state_observer.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
-import {
- LOADING_CONTENT_EVENT,
- LOADING_SUCCESS_EVENT,
- LOADING_ERROR_EVENT,
- ALERT_EVENT,
-} from '~/content_editor/constants';
+import { ALERT_EVENT } from '~/content_editor/constants';
import { createTestEditor } from '../test_utils';
describe('content_editor/components/editor_state_observer', () => {
@@ -18,9 +13,6 @@ describe('content_editor/components/editor_state_observer', () => {
let onDocUpdateListener;
let onSelectionUpdateListener;
let onTransactionListener;
- let onLoadingContentListener;
- let onLoadingSuccessListener;
- let onLoadingErrorListener;
let onAlertListener;
let eventHub;
@@ -38,9 +30,6 @@ describe('content_editor/components/editor_state_observer', () => {
selectionUpdate: onSelectionUpdateListener,
transaction: onTransactionListener,
[ALERT_EVENT]: onAlertListener,
- [LOADING_CONTENT_EVENT]: onLoadingContentListener,
- [LOADING_SUCCESS_EVENT]: onLoadingSuccessListener,
- [LOADING_ERROR_EVENT]: onLoadingErrorListener,
},
});
};
@@ -50,9 +39,6 @@ describe('content_editor/components/editor_state_observer', () => {
onSelectionUpdateListener = jest.fn();
onTransactionListener = jest.fn();
onAlertListener = jest.fn();
- onLoadingSuccessListener = jest.fn();
- onLoadingContentListener = jest.fn();
- onLoadingErrorListener = jest.fn();
buildEditor();
});
@@ -81,11 +67,8 @@ describe('content_editor/components/editor_state_observer', () => {
});
it.each`
- event | listener
- ${ALERT_EVENT} | ${() => onAlertListener}
- ${LOADING_CONTENT_EVENT} | ${() => onLoadingContentListener}
- ${LOADING_SUCCESS_EVENT} | ${() => onLoadingSuccessListener}
- ${LOADING_ERROR_EVENT} | ${() => onLoadingErrorListener}
+ event | listener
+ ${ALERT_EVENT} | ${() => onAlertListener}
`('listens to $event event in the eventBus object', ({ event, listener }) => {
const args = {};
@@ -114,9 +97,6 @@ describe('content_editor/components/editor_state_observer', () => {
it.each`
event
${ALERT_EVENT}
- ${LOADING_CONTENT_EVENT}
- ${LOADING_SUCCESS_EVENT}
- ${LOADING_ERROR_EVENT}
`('removes $event event hook from eventHub', ({ event }) => {
jest.spyOn(eventHub, '$off');
jest.spyOn(eventHub, '$on');
diff --git a/spec/frontend/content_editor/components/loading_indicator_spec.js b/spec/frontend/content_editor/components/loading_indicator_spec.js
index e4fb09b70a4..0065103d01b 100644
--- a/spec/frontend/content_editor/components/loading_indicator_spec.js
+++ b/spec/frontend/content_editor/components/loading_indicator_spec.js
@@ -1,18 +1,10 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import LoadingIndicator from '~/content_editor/components/loading_indicator.vue';
-import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue';
-import {
- LOADING_CONTENT_EVENT,
- LOADING_SUCCESS_EVENT,
- LOADING_ERROR_EVENT,
-} from '~/content_editor/constants';
describe('content_editor/components/loading_indicator', () => {
let wrapper;
- const findEditorStateObserver = () => wrapper.findComponent(EditorStateObserver);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const createWrapper = () => {
@@ -24,48 +16,12 @@ describe('content_editor/components/loading_indicator', () => {
});
describe('when loading content', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createWrapper();
-
- findEditorStateObserver().vm.$emit(LOADING_CONTENT_EVENT);
-
- await nextTick();
});
it('displays loading indicator', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
});
-
- describe('when loading content succeeds', () => {
- beforeEach(async () => {
- createWrapper();
-
- findEditorStateObserver().vm.$emit(LOADING_CONTENT_EVENT);
- await nextTick();
- findEditorStateObserver().vm.$emit(LOADING_SUCCESS_EVENT);
- await nextTick();
- });
-
- it('hides loading indicator', () => {
- expect(findLoadingIcon().exists()).toBe(false);
- });
- });
-
- describe('when loading content fails', () => {
- const error = 'error';
-
- beforeEach(async () => {
- createWrapper();
-
- findEditorStateObserver().vm.$emit(LOADING_CONTENT_EVENT);
- await nextTick();
- findEditorStateObserver().vm.$emit(LOADING_ERROR_EVENT, error);
- await nextTick();
- });
-
- it('hides loading indicator', () => {
- expect(findLoadingIcon().exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/content_editor/components/toolbar_image_button_spec.js b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
index dab7e67d7c5..5473d43f5a1 100644
--- a/spec/frontend/content_editor/components/toolbar_image_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
@@ -1,8 +1,9 @@
-import { GlButton, GlFormInputGroup } from '@gitlab/ui';
+import { GlButton, GlFormInputGroup, GlDropdown } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarImageButton from '~/content_editor/components/toolbar_image_button.vue';
import Attachment from '~/content_editor/extensions/attachment';
import Image from '~/content_editor/extensions/image';
+import { stubComponent } from 'helpers/stub_component';
import { createTestEditor, mockChainedCommands } from '../test_utils';
describe('content_editor/components/toolbar_image_button', () => {
@@ -14,15 +15,19 @@ describe('content_editor/components/toolbar_image_button', () => {
provide: {
tiptapEditor: editor,
},
+ stubs: {
+ GlDropdown: stubComponent(GlDropdown),
+ },
});
};
const findImageURLInput = () =>
wrapper.findComponent(GlFormInputGroup).find('input[type="text"]');
const findApplyImageButton = () => wrapper.findComponent(GlButton);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
const selectFile = async (file) => {
- const input = wrapper.find({ ref: 'fileSelector' });
+ const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });
@@ -77,4 +82,16 @@ describe('content_editor/components/toolbar_image_button', () => {
expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'image', value: 'upload' }]);
});
+
+ describe('a11y tests', () => {
+ it('sets text, title, and text-sr-only properties to the table button dropdown', () => {
+ buildWrapper();
+
+ expect(findDropdown().props()).toMatchObject({
+ text: 'Insert image',
+ textSrOnly: true,
+ });
+ expect(findDropdown().attributes('title')).toBe('Insert image');
+ });
+ });
});
diff --git a/spec/frontend/content_editor/components/toolbar_link_button_spec.js b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
index fc26a9da471..40e859e96af 100644
--- a/spec/frontend/content_editor/components/toolbar_link_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
@@ -4,6 +4,7 @@ import ToolbarLinkButton from '~/content_editor/components/toolbar_link_button.v
import eventHubFactory from '~/helpers/event_hub_factory';
import Link from '~/content_editor/extensions/link';
import { hasSelection } from '~/content_editor/services/utils';
+import { stubComponent } from 'helpers/stub_component';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../test_utils';
jest.mock('~/content_editor/services/utils');
@@ -18,6 +19,9 @@ describe('content_editor/components/toolbar_link_button', () => {
tiptapEditor: editor,
eventHub: eventHubFactory(),
},
+ stubs: {
+ GlDropdown: stubComponent(GlDropdown),
+ },
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
@@ -26,7 +30,7 @@ describe('content_editor/components/toolbar_link_button', () => {
const findRemoveLinkButton = () => wrapper.findByText('Remove link');
const selectFile = async (file) => {
- const input = wrapper.find({ ref: 'fileSelector' });
+ const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });
@@ -205,4 +209,16 @@ describe('content_editor/components/toolbar_link_button', () => {
});
});
});
+
+ describe('a11y tests', () => {
+ it('sets text, title, and text-sr-only properties to the table button dropdown', () => {
+ buildWrapper();
+
+ expect(findDropdown().props()).toMatchObject({
+ text: 'Insert link',
+ textSrOnly: true,
+ });
+ expect(findDropdown().attributes('title')).toBe('Insert link');
+ });
+ });
});
diff --git a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
index 62fec8d4e72..a23f8370adf 100644
--- a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
@@ -1,8 +1,10 @@
+import { GlDropdown } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarMoreDropdown from '~/content_editor/components/toolbar_more_dropdown.vue';
import Diagram from '~/content_editor/extensions/diagram';
import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
import eventHubFactory from '~/helpers/event_hub_factory';
+import { stubComponent } from 'helpers/stub_component';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../test_utils';
describe('content_editor/components/toolbar_more_dropdown', () => {
@@ -23,10 +25,15 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
tiptapEditor,
eventHub,
},
+ stubs: {
+ GlDropdown: stubComponent(GlDropdown),
+ },
propsData,
});
};
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+
beforeEach(() => {
buildEditor();
buildWrapper();
@@ -67,4 +74,14 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
expect(wrapper.emitted('execute')).toEqual([[{ contentType }]]);
});
});
+
+ describe('a11y tests', () => {
+ it('sets text, title, and text-sr-only properties to the table button dropdown', () => {
+ expect(findDropdown().props()).toMatchObject({
+ text: 'More',
+ textSrOnly: true,
+ });
+ expect(findDropdown().attributes('title')).toBe('More');
+ });
+ });
});
diff --git a/spec/frontend/content_editor/components/toolbar_table_button_spec.js b/spec/frontend/content_editor/components/toolbar_table_button_spec.js
index 056e5e04e1f..aa4604661e5 100644
--- a/spec/frontend/content_editor/components/toolbar_table_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_table_button_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlButton } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarTableButton from '~/content_editor/components/toolbar_table_button.vue';
+import { stubComponent } from 'helpers/stub_component';
import { createTestEditor, mockChainedCommands } from '../test_utils';
describe('content_editor/components/toolbar_table_button', () => {
@@ -12,6 +13,9 @@ describe('content_editor/components/toolbar_table_button', () => {
provide: {
tiptapEditor: editor,
},
+ stubs: {
+ GlDropdown: stubComponent(GlDropdown),
+ },
});
};
@@ -98,4 +102,14 @@ describe('content_editor/components/toolbar_table_button', () => {
expect(getNumButtons()).toBe(100); // 10x10 (and not 11x11)
});
+
+ describe('a11y tests', () => {
+ it('sets text, title, and text-sr-only properties to the table button dropdown', () => {
+ expect(findDropdown().props()).toMatchObject({
+ text: 'Insert table',
+ textSrOnly: true,
+ });
+ expect(findDropdown().attributes('title')).toBe('Insert table');
+ });
+ });
});
diff --git a/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
index 608be1bd693..3ebb305afbf 100644
--- a/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
@@ -53,7 +53,7 @@ describe('content_editor/components/toolbar_text_style_dropdown', () => {
});
});
- describe('when there is an active item ', () => {
+ describe('when there is an active item', () => {
let activeTextStyle;
beforeEach(async () => {
@@ -68,7 +68,7 @@ describe('content_editor/components/toolbar_text_style_dropdown', () => {
await emitEditorEvent({ event: 'transaction', tiptapEditor });
});
- it('displays the active text style label as the dropdown toggle text ', () => {
+ it('displays the active text style label as the dropdown toggle text', () => {
expect(findDropdown().props().text).toBe(activeTextStyle.label);
});
diff --git a/spec/frontend/content_editor/components/wrappers/code_block_spec.js b/spec/frontend/content_editor/components/wrappers/code_block_spec.js
index 17a365e12bb..a5ef19fb8e8 100644
--- a/spec/frontend/content_editor/components/wrappers/code_block_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/code_block_spec.js
@@ -104,7 +104,7 @@ describe('content/components/wrappers/code_block', () => {
it('does not render a preview if showPreview: false', async () => {
createWrapper({ language: 'plantuml', isDiagram: true, showPreview: false });
- expect(wrapper.find({ ref: 'diagramContainer' }).exists()).toBe(false);
+ expect(wrapper.findComponent({ ref: 'diagramContainer' }).exists()).toBe(false);
});
it('does not update preview when diagram is not active', async () => {
@@ -134,7 +134,7 @@ describe('content/components/wrappers/code_block', () => {
await nextTick();
expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram');
- expect(wrapper.find(SandboxedMermaid).exists()).toBe(false);
+ expect(wrapper.findComponent(SandboxedMermaid).exists()).toBe(false);
});
it('renders an iframe with preview for a mermaid diagram', async () => {
@@ -143,7 +143,7 @@ describe('content/components/wrappers/code_block', () => {
await emitEditorEvent({ event: 'transaction', tiptapEditor });
await nextTick();
- expect(wrapper.find(SandboxedMermaid).props('source')).toBe('');
+ expect(wrapper.findComponent(SandboxedMermaid).props('source')).toBe('');
expect(wrapper.find('img').exists()).toBe(false);
});
});
diff --git a/spec/frontend/content_editor/extensions/paste_markdown_spec.js b/spec/frontend/content_editor/extensions/paste_markdown_spec.js
index 53efda6aee2..30e798e8817 100644
--- a/spec/frontend/content_editor/extensions/paste_markdown_spec.js
+++ b/spec/frontend/content_editor/extensions/paste_markdown_spec.js
@@ -5,12 +5,7 @@ import Frontmatter from '~/content_editor/extensions/frontmatter';
import Bold from '~/content_editor/extensions/bold';
import { VARIANT_DANGER } from '~/flash';
import eventHubFactory from '~/helpers/event_hub_factory';
-import {
- ALERT_EVENT,
- LOADING_CONTENT_EVENT,
- LOADING_SUCCESS_EVENT,
- LOADING_ERROR_EVENT,
-} from '~/content_editor/constants';
+import { ALERT_EVENT } from '~/content_editor/constants';
import waitForPromises from 'helpers/wait_for_promises';
import { createTestEditor, createDocBuilder, waitUntilNextDocTransaction } from '../test_utils';
@@ -115,13 +110,6 @@ describe('content_editor/extensions/paste_markdown', () => {
expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON());
});
-
- it(`triggers ${LOADING_SUCCESS_EVENT}`, async () => {
- await triggerPasteEventHandlerAndWaitForTransaction(buildClipboardEvent());
-
- expect(eventHub.$emit).toHaveBeenCalledWith(LOADING_CONTENT_EVENT);
- expect(eventHub.$emit).toHaveBeenCalledWith(LOADING_SUCCESS_EVENT);
- });
});
describe('when rendering markdown fails', () => {
@@ -129,13 +117,6 @@ describe('content_editor/extensions/paste_markdown', () => {
renderMarkdown.mockRejectedValueOnce();
});
- it(`triggers ${LOADING_ERROR_EVENT} event`, async () => {
- await triggerPasteEventHandler(buildClipboardEvent());
- await waitForPromises();
-
- expect(eventHub.$emit).toHaveBeenCalledWith(LOADING_ERROR_EVENT);
- });
-
it(`triggers ${ALERT_EVENT} event`, async () => {
await triggerPasteEventHandler(buildClipboardEvent());
await waitForPromises();
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index 7ae0a7c13c1..bc43af9bd8b 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -1,8 +1,10 @@
+import Audio from '~/content_editor/extensions/audio';
import Bold from '~/content_editor/extensions/bold';
import Blockquote from '~/content_editor/extensions/blockquote';
import BulletList from '~/content_editor/extensions/bullet_list';
import Code from '~/content_editor/extensions/code';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
+import Diagram from '~/content_editor/extensions/diagram';
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
import Frontmatter from '~/content_editor/extensions/frontmatter';
@@ -21,22 +23,27 @@ import Sourcemap from '~/content_editor/extensions/sourcemap';
import Strike from '~/content_editor/extensions/strike';
import Table from '~/content_editor/extensions/table';
import TableHeader from '~/content_editor/extensions/table_header';
+import TableOfContents from '~/content_editor/extensions/table_of_contents';
import TableRow from '~/content_editor/extensions/table_row';
import TableCell from '~/content_editor/extensions/table_cell';
import TaskList from '~/content_editor/extensions/task_list';
import TaskItem from '~/content_editor/extensions/task_item';
+import Video from '~/content_editor/extensions/video';
import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
+import { SAFE_VIDEO_EXT, SAFE_AUDIO_EXT, DIAGRAM_LANGUAGES } from '~/content_editor/constants';
import { createTestEditor, createDocBuilder } from './test_utils';
const tiptapEditor = createTestEditor({
extensions: [
+ Audio,
Blockquote,
Bold,
BulletList,
Code,
CodeBlockHighlight,
+ Diagram,
FootnoteDefinition,
FootnoteReference,
Frontmatter,
@@ -55,8 +62,10 @@ const tiptapEditor = createTestEditor({
TableRow,
TableHeader,
TableCell,
+ TableOfContents,
TaskList,
TaskItem,
+ Video,
...HTMLNodes,
],
});
@@ -65,12 +74,14 @@ const {
builders: {
doc,
paragraph,
+ audio,
bold,
blockquote,
bulletList,
code,
codeBlock,
div,
+ diagram,
footnoteDefinition,
footnoteReference,
frontmatter,
@@ -89,17 +100,21 @@ const {
tableRow,
tableHeader,
tableCell,
+ tableOfContents,
taskItem,
taskList,
+ video,
},
} = createDocBuilder({
tiptapEditor,
names: {
+ audio: { nodeType: Audio.name },
blockquote: { nodeType: Blockquote.name },
bold: { markType: Bold.name },
bulletList: { nodeType: BulletList.name },
code: { markType: Code.name },
codeBlock: { nodeType: CodeBlockHighlight.name },
+ diagram: { nodeType: Diagram.name },
footnoteDefinition: { nodeType: FootnoteDefinition.name },
footnoteReference: { nodeType: FootnoteReference.name },
frontmatter: { nodeType: Frontmatter.name },
@@ -118,8 +133,10 @@ const {
tableCell: { nodeType: TableCell.name },
tableHeader: { nodeType: TableHeader.name },
tableRow: { nodeType: TableRow.name },
+ tableOfContents: { nodeType: TableOfContents.name },
taskItem: { nodeType: TaskItem.name },
taskList: { nodeType: TaskList.name },
+ video: { nodeType: Video.name },
...HTMLNodes.reduce(
(builders, htmlNode) => ({
...builders,
@@ -1233,6 +1250,62 @@ title: 'layout'
),
),
},
+ ...SAFE_AUDIO_EXT.map((extension) => {
+ const src = `http://test.host/video.${extension}`;
+ const markdown = `![audio](${src})`;
+
+ return {
+ markdown,
+ expectedDoc: doc(
+ paragraph(
+ source(markdown),
+ audio({
+ ...source(markdown),
+ canonicalSrc: src,
+ src,
+ alt: 'audio',
+ }),
+ ),
+ ),
+ };
+ }),
+ ...SAFE_VIDEO_EXT.map((extension) => {
+ const src = `http://test.host/video.${extension}`;
+ const markdown = `![video](${src})`;
+
+ return {
+ markdown,
+ expectedDoc: doc(
+ paragraph(
+ source(markdown),
+ video({
+ ...source(markdown),
+ canonicalSrc: src,
+ src,
+ alt: 'video',
+ }),
+ ),
+ ),
+ };
+ }),
+ ...DIAGRAM_LANGUAGES.map((language) => {
+ const markdown = `\`\`\`${language}
+content
+\`\`\``;
+
+ return {
+ markdown,
+ expectedDoc: doc(diagram({ ...source(markdown), language }, 'content')),
+ };
+ }),
+ {
+ markdown: '[[_TOC_]]',
+ expectedDoc: doc(tableOfContents(source('[[_TOC_]]'))),
+ },
+ {
+ markdown: '[TOC]',
+ expectedDoc: doc(tableOfContents(source('[TOC]'))),
+ },
];
const runOnly = examples.find((example) => example.only === true);
diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
index 4a57c7b1942..bd48b7fdd23 100644
--- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
+++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
@@ -1,6 +1,7 @@
import { DOMSerializer } from 'prosemirror-model';
// TODO: DRY up duplication with spec/frontend/content_editor/services/markdown_serializer_spec.js
// See https://gitlab.com/groups/gitlab-org/-/epics/7719#plan
+import Audio from '~/content_editor/extensions/audio';
import Blockquote from '~/content_editor/extensions/blockquote';
import Bold from '~/content_editor/extensions/bold';
import BulletList from '~/content_editor/extensions/bullet_list';
@@ -33,13 +34,16 @@ import Table from '~/content_editor/extensions/table';
import TableCell from '~/content_editor/extensions/table_cell';
import TableHeader from '~/content_editor/extensions/table_header';
import TableRow from '~/content_editor/extensions/table_row';
+import TableOfContents from '~/content_editor/extensions/table_of_contents';
import TaskItem from '~/content_editor/extensions/task_item';
import TaskList from '~/content_editor/extensions/task_list';
+import Video from '~/content_editor/extensions/video';
import createMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestEditor } from 'jest/content_editor/test_utils';
const tiptapEditor = createTestEditor({
extensions: [
+ Audio,
Blockquote,
Bold,
BulletList,
@@ -72,8 +76,10 @@ const tiptapEditor = createTestEditor({
TableCell,
TableHeader,
TableRow,
+ TableOfContents,
TaskItem,
TaskList,
+ Video,
],
});
diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js
index a3553e612ca..6175cbdd3d4 100644
--- a/spec/frontend/content_editor/services/content_editor_spec.js
+++ b/spec/frontend/content_editor/services/content_editor_spec.js
@@ -1,8 +1,3 @@
-import {
- LOADING_CONTENT_EVENT,
- LOADING_SUCCESS_EVENT,
- LOADING_ERROR_EVENT,
-} from '~/content_editor/constants';
import { ContentEditor } from '~/content_editor/services/content_editor';
import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor, createDocBuilder } from '../test_utils';
@@ -14,6 +9,7 @@ describe('content_editor/services/content_editor', () => {
let eventHub;
let doc;
let p;
+ const testMarkdown = '**bold text**';
beforeEach(() => {
const tiptapEditor = createTestEditor();
@@ -36,6 +32,9 @@ describe('content_editor/services/content_editor', () => {
});
});
+ const testDoc = () => doc(p('document'));
+ const testEmptyDoc = () => doc();
+
describe('.dispose', () => {
it('destroys the tiptapEditor', () => {
expect(contentEditor.tiptapEditor.destroy).not.toHaveBeenCalled();
@@ -46,51 +45,77 @@ describe('content_editor/services/content_editor', () => {
});
});
- describe('when setSerializedContent succeeds', () => {
- let document;
- const languages = ['javascript'];
- const testMarkdown = '**bold text**';
+ describe('empty', () => {
+ it('returns true when tiptapEditor is empty', async () => {
+ deserializer.deserialize.mockResolvedValueOnce({ document: testEmptyDoc() });
+
+ await contentEditor.setSerializedContent(testMarkdown);
- beforeEach(() => {
- document = doc(p('document'));
- deserializer.deserialize.mockResolvedValueOnce({ document, languages });
+ expect(contentEditor.empty).toBe(true);
});
- it('emits loadingContent and loadingSuccess event in the eventHub', () => {
- let loadingContentEmitted = false;
+ it('returns false when tiptapEditor is not empty', async () => {
+ deserializer.deserialize.mockResolvedValueOnce({ document: testDoc() });
- eventHub.$on(LOADING_CONTENT_EVENT, () => {
- loadingContentEmitted = true;
- });
- eventHub.$on(LOADING_SUCCESS_EVENT, () => {
- expect(loadingContentEmitted).toBe(true);
- });
+ await contentEditor.setSerializedContent(testMarkdown);
- contentEditor.setSerializedContent(testMarkdown);
+ expect(contentEditor.empty).toBe(false);
});
+ });
- it('sets the deserialized document in the tiptap editor object', async () => {
- await contentEditor.setSerializedContent(testMarkdown);
+ describe('editable', () => {
+ it('returns true when tiptapEditor is editable', async () => {
+ contentEditor.setEditable(true);
- expect(contentEditor.tiptapEditor.state.doc.toJSON()).toEqual(document.toJSON());
+ expect(contentEditor.editable).toBe(true);
+ });
+
+ it('returns false when tiptapEditor is readonly', async () => {
+ contentEditor.setEditable(false);
+
+ expect(contentEditor.editable).toBe(false);
});
});
- describe('when setSerializedContent fails', () => {
- const error = 'error';
+ describe('changed', () => {
+ it('returns true when the initial document changes', async () => {
+ deserializer.deserialize.mockResolvedValueOnce({ document: testDoc() });
+
+ await contentEditor.setSerializedContent(testMarkdown);
+
+ contentEditor.tiptapEditor.commands.insertContent(' new content');
+
+ expect(contentEditor.changed).toBe(true);
+ });
+
+ it('returns false when the initial document hasn’t changed', async () => {
+ deserializer.deserialize.mockResolvedValueOnce({ document: testDoc() });
+
+ await contentEditor.setSerializedContent(testMarkdown);
+
+ expect(contentEditor.changed).toBe(false);
+ });
+
+ it('returns false when an initial document is not set and the document is empty', () => {
+ expect(contentEditor.changed).toBe(false);
+ });
- beforeEach(() => {
- deserializer.deserialize.mockRejectedValueOnce(error);
+ it('returns true when an initial document is not set and the document is not empty', () => {
+ contentEditor.tiptapEditor.commands.insertContent('new content');
+
+ expect(contentEditor.changed).toBe(true);
});
+ });
+
+ describe('when setSerializedContent succeeds', () => {
+ it('sets the deserialized document in the tiptap editor object', async () => {
+ const document = testDoc();
+
+ deserializer.deserialize.mockResolvedValueOnce({ document });
- it('emits loadingError event', async () => {
- eventHub.$on(LOADING_ERROR_EVENT, (e) => {
- expect(e).toBe('error');
- });
+ await contentEditor.setSerializedContent(testMarkdown);
- await expect(() => contentEditor.setSerializedContent('**bold text**')).rejects.toEqual(
- error,
- );
+ expect(contentEditor.tiptapEditor.state.doc.toJSON()).toEqual(document.toJSON());
});
});
});
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 0e5281be9bf..56394c85e8b 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -1,3 +1,4 @@
+import Audio from '~/content_editor/extensions/audio';
import Blockquote from '~/content_editor/extensions/blockquote';
import Bold from '~/content_editor/extensions/bold';
import BulletList from '~/content_editor/extensions/bullet_list';
@@ -33,6 +34,7 @@ import TableHeader from '~/content_editor/extensions/table_header';
import TableRow from '~/content_editor/extensions/table_row';
import TaskItem from '~/content_editor/extensions/task_item';
import TaskList from '~/content_editor/extensions/task_list';
+import Video from '~/content_editor/extensions/video';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestEditor, createDocBuilder } from '../test_utils';
@@ -41,6 +43,7 @@ jest.mock('~/emoji');
const tiptapEditor = createTestEditor({
extensions: [
+ Audio,
Blockquote,
Bold,
BulletList,
@@ -73,6 +76,7 @@ const tiptapEditor = createTestEditor({
TableRow,
TaskItem,
TaskList,
+ Video,
...HTMLMarks,
...HTMLNodes,
],
@@ -80,6 +84,7 @@ const tiptapEditor = createTestEditor({
const {
builders: {
+ audio,
doc,
blockquote,
bold,
@@ -114,6 +119,7 @@ const {
tableRow,
taskItem,
taskList,
+ video,
},
} = createDocBuilder({
tiptapEditor,
@@ -1230,6 +1236,21 @@ paragraph
);
});
+ it('serializes audio and video elements', () => {
+ expect(
+ serialize(
+ paragraph(
+ audio({ alt: 'audio', canonicalSrc: 'audio.mp3' }),
+ ' and ',
+ video({ alt: 'video', canonicalSrc: 'video.mov' }),
+ ),
+ ),
+ ).toBe(
+ `
+![audio](audio.mp3) and ![video](video.mov)`.trimLeft(),
+ );
+ });
+
const defaultEditAction = (initialContent) => {
tiptapEditor.chain().setContent(initialContent.toJSON()).insertContent(' modified').run();
};
diff --git a/spec/frontend/crm/form_spec.js b/spec/frontend/crm/form_spec.js
index f0e9150cada..57e28b396cf 100644
--- a/spec/frontend/crm/form_spec.js
+++ b/spec/frontend/crm/form_spec.js
@@ -298,7 +298,7 @@ describe('Reusable form component', () => {
`(
'should render the correct component for #$id with the value "$value"',
({ index, id, component, value }) => {
- const findFormElement = () => findFormGroup(index).find(component);
+ const findFormElement = () => findFormGroup(index).findComponent(component);
expect(findFormElement().attributes('id')).toBe(id);
expect(findFormElement().attributes('value')).toBe(value);
@@ -307,7 +307,8 @@ describe('Reusable form component', () => {
it('should render a checked GlFormCheckbox for #active', () => {
const activeCheckboxIndex = 6;
- const findFormElement = () => findFormGroup(activeCheckboxIndex).find(GlFormCheckbox);
+ const findFormElement = () =>
+ findFormGroup(activeCheckboxIndex).findComponent(GlFormCheckbox);
expect(findFormElement().attributes('id')).toBe('active');
expect(findFormElement().attributes('checked')).toBe('true');
diff --git a/spec/frontend/crm/mock_data.js b/spec/frontend/crm/mock_data.js
index a2e2e88ac60..a19ee01c2a5 100644
--- a/spec/frontend/crm/mock_data.js
+++ b/spec/frontend/crm/mock_data.js
@@ -102,6 +102,13 @@ export const getGroupOrganizationsQueryResponse = {
active: true,
},
],
+ pageInfo: {
+ __typename: 'PageInfo',
+ hasNextPage: false,
+ endCursor: 'eyJsYXN0X25hbWUiOiJMZWRuZXIiLCJpZCI6IjE3OSJ9',
+ hasPreviousPage: false,
+ startCursor: 'eyJsYXN0X25hbWUiOiJCYXJ0b24iLCJpZCI6IjE5MyJ9',
+ },
},
},
},
@@ -155,6 +162,21 @@ export const updateContactMutationResponse = {
},
};
+export const getGroupOrganizationsCountQueryResponse = {
+ data: {
+ group: {
+ __typename: 'Group',
+ id: 'gid://gitlab/Group/26',
+ organizationStateCounts: {
+ all: 24,
+ active: 21,
+ inactive: 3,
+ __typename: 'OrganizationStateCountsType',
+ },
+ },
+ },
+};
+
export const updateContactMutationErrorResponse = {
data: {
customerRelationsContactUpdate: {
diff --git a/spec/frontend/crm/organizations_root_spec.js b/spec/frontend/crm/organizations_root_spec.js
index 1780a5945a6..a0b56596177 100644
--- a/spec/frontend/crm/organizations_root_spec.js
+++ b/spec/frontend/crm/organizations_root_spec.js
@@ -1,14 +1,19 @@
-import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
-import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import OrganizationsRoot from '~/crm/organizations/components/organizations_root.vue';
import routes from '~/crm/organizations/routes';
import getGroupOrganizationsQuery from '~/crm/organizations/components/graphql/get_group_organizations.query.graphql';
-import { getGroupOrganizationsQueryResponse } from './mock_data';
+import getGroupOrganizationsCountByStateQuery from '~/crm/organizations/components/graphql/get_group_organizations_count_by_state.query.graphql';
+import PaginatedTableWithSearchAndTabs from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue';
+import {
+ getGroupOrganizationsQueryResponse,
+ getGroupOrganizationsCountQueryResponse,
+} from './mock_data';
describe('Customer relations organizations root app', () => {
Vue.use(VueApollo);
@@ -21,23 +26,31 @@ describe('Customer relations organizations root app', () => {
const findRowByName = (rowName) => wrapper.findAllByRole('row', { name: rowName });
const findIssuesLinks = () => wrapper.findAllByTestId('issues-link');
const findNewOrganizationButton = () => wrapper.findByTestId('new-organization-button');
- const findError = () => wrapper.findComponent(GlAlert);
+ const findTable = () => wrapper.findComponent(PaginatedTableWithSearchAndTabs);
const successQueryHandler = jest.fn().mockResolvedValue(getGroupOrganizationsQueryResponse);
+ const successCountQueryHandler = jest
+ .fn()
+ .mockResolvedValue(getGroupOrganizationsCountQueryResponse);
const basePath = '/groups/flightjs/-/crm/organizations';
const mountComponent = ({
queryHandler = successQueryHandler,
- mountFunction = shallowMountExtended,
+ countQueryHandler = successCountQueryHandler,
canAdminCrmOrganization = true,
+ textQuery = null,
} = {}) => {
- fakeApollo = createMockApollo([[getGroupOrganizationsQuery, queryHandler]]);
- wrapper = mountFunction(OrganizationsRoot, {
+ fakeApollo = createMockApollo([
+ [getGroupOrganizationsQuery, queryHandler],
+ [getGroupOrganizationsCountByStateQuery, countQueryHandler],
+ ]);
+ wrapper = mountExtended(OrganizationsRoot, {
router,
provide: {
canAdminCrmOrganization,
groupFullPath: 'flightjs',
groupIssuesPath: '/issues',
+ textQuery,
},
apolloProvider: fakeApollo,
});
@@ -57,9 +70,33 @@ describe('Customer relations organizations root app', () => {
router = null;
});
- it('should render loading spinner', () => {
+ it('should render table with default props and loading spinner', () => {
mountComponent();
+ expect(findTable().props()).toMatchObject({
+ items: [],
+ itemsCount: {},
+ pageInfo: {},
+ statusTabs: [
+ { title: 'Active', status: 'ACTIVE', filters: 'active' },
+ { title: 'Inactive', status: 'INACTIVE', filters: 'inactive' },
+ { title: 'All', status: 'ALL', filters: 'all' },
+ ],
+ showItems: true,
+ showErrorMsg: false,
+ trackViewsOptions: { category: 'Customer Relations', action: 'view_organizations_list' },
+ i18n: {
+ emptyText: 'No organizations found',
+ issuesButtonLabel: 'View issues',
+ editButtonLabel: 'Edit',
+ title: 'Customer relations organizations',
+ newOrganization: 'New organization',
+ errorText: 'Something went wrong. Please try again.',
+ },
+ serverErrorMessage: '',
+ filterSearchKey: 'organizations',
+ filterSearchTokens: [],
+ });
expect(findLoadingIcon().exists()).toBe(true);
});
@@ -77,11 +114,25 @@ describe('Customer relations organizations root app', () => {
});
});
- it('should render error message on reject', async () => {
- mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
- await waitForPromises();
+ describe('error', () => {
+ it('should render on reject', async () => {
+ mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain('Something went wrong. Please try again.');
+ });
+
+ it('should be removed on error-alert-dismissed event', async () => {
+ mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
+ await waitForPromises();
- expect(findError().exists()).toBe(true);
+ expect(wrapper.text()).toContain('Something went wrong. Please try again.');
+
+ findTable().vm.$emit('error-alert-dismissed');
+ await waitForPromises();
+
+ expect(wrapper.text()).not.toContain('Something went wrong. Please try again.');
+ });
});
describe('on successful load', () => {
@@ -89,20 +140,27 @@ describe('Customer relations organizations root app', () => {
mountComponent();
await waitForPromises();
- expect(findError().exists()).toBe(false);
+ expect(wrapper.text()).not.toContain('Something went wrong. Please try again.');
});
it('renders correct results', async () => {
- mountComponent({ mountFunction: mountExtended });
+ mountComponent();
await waitForPromises();
expect(findRowByName(/Test Inc/i)).toHaveLength(1);
expect(findRowByName(/VIP/i)).toHaveLength(1);
expect(findRowByName(/120/i)).toHaveLength(1);
- const issueLink = findIssuesLinks().at(0);
- expect(issueLink.exists()).toBe(true);
- expect(issueLink.attributes('href')).toBe('/issues?crm_organization_id=2');
+ expect(findIssuesLinks()).toHaveLength(3);
+
+ const links = findIssuesLinks().wrappers.map((w) => w.attributes('href'));
+ expect(links).toEqual(
+ expect.arrayContaining([
+ '/issues?crm_organization_id=1',
+ '/issues?crm_organization_id=2',
+ '/issues?crm_organization_id=3',
+ ]),
+ );
});
});
});
diff --git a/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap b/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap
index 7f211c1028e..92927ef16ec 100644
--- a/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap
+++ b/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap
@@ -1,28 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`TotalTime with a blank object should render -- 1`] = `"<span class=\\"total-time\\"> -- </span>"`;
+exports[`TotalTime with a blank object should render -- 1`] = `"<span> -- </span>"`;
exports[`TotalTime with a valid time object with {"days": 3, "mins": 47, "seconds": 3} 1`] = `
-"<span class=\\"total-time\\">
+"<span>
3 <span>days</span></span>"
`;
exports[`TotalTime with a valid time object with {"hours": 7, "mins": 20, "seconds": 10} 1`] = `
-"<span class=\\"total-time\\">
+"<span>
7 <span>hrs</span></span>"
`;
exports[`TotalTime with a valid time object with {"hours": 23, "mins": 10} 1`] = `
-"<span class=\\"total-time\\">
+"<span>
23 <span>hrs</span></span>"
`;
exports[`TotalTime with a valid time object with {"mins": 47, "seconds": 3} 1`] = `
-"<span class=\\"total-time\\">
+"<span>
47 <span>mins</span></span>"
`;
exports[`TotalTime with a valid time object with {"seconds": 35} 1`] = `
-"<span class=\\"total-time\\">
+"<span>
35 <span>s</span></span>"
`;
diff --git a/spec/frontend/cycle_analytics/base_spec.js b/spec/frontend/cycle_analytics/base_spec.js
index ea3da86c7b2..013bea671a8 100644
--- a/spec/frontend/cycle_analytics/base_spec.js
+++ b/spec/frontend/cycle_analytics/base_spec.js
@@ -201,7 +201,7 @@ describe('Value stream analytics component', () => {
it('renders the stage table with a loading icon', () => {
const tableWrapper = findStageTable();
expect(tableWrapper.exists()).toBe(true);
- expect(tableWrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(tableWrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders the path navigation loading state', () => {
diff --git a/spec/frontend/cycle_analytics/path_navigation_spec.js b/spec/frontend/cycle_analytics/path_navigation_spec.js
index fa9eadbd071..fec1526359c 100644
--- a/spec/frontend/cycle_analytics/path_navigation_spec.js
+++ b/spec/frontend/cycle_analytics/path_navigation_spec.js
@@ -56,7 +56,9 @@ describe('Project PathNavigation', () => {
describe('displays correctly', () => {
it('has the correct props', () => {
- expect(wrapper.find(GlPath).props('items')).toMatchObject(transformedProjectStagePathData);
+ expect(wrapper.findComponent(GlPath).props('items')).toMatchObject(
+ transformedProjectStagePathData,
+ );
});
it('contains all the expected stages', () => {
@@ -69,11 +71,11 @@ describe('Project PathNavigation', () => {
describe('loading', () => {
describe('is false', () => {
it('displays the gl-path component', () => {
- expect(wrapper.find(GlPath).exists()).toBe(true);
+ expect(wrapper.findComponent(GlPath).exists()).toBe(true);
});
it('hides the gl-skeleton-loading component', () => {
- expect(wrapper.find(GlSkeletonLoader).exists()).toBe(false);
+ expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
});
it('renders each stage', () => {
@@ -112,11 +114,11 @@ describe('Project PathNavigation', () => {
});
it('hides the gl-path component', () => {
- expect(wrapper.find(GlPath).exists()).toBe(false);
+ expect(wrapper.findComponent(GlPath).exists()).toBe(false);
});
it('displays the gl-skeleton-loading component', () => {
- expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
+ expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
index 23e41f35b00..9c8cd6a3dbc 100644
--- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
+++ b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
@@ -176,7 +176,7 @@ describe('ValueStreamMetrics', () => {
await waitForPromises();
});
- it('it should render an error message', () => {
+ it('should render an error message', () => {
expect(createFlash).toHaveBeenCalledWith({
message: `There was an error while fetching value stream analytics ${fakeReqName} data.`,
});
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 7c46c280d46..bbafdc000db 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
@@ -42,7 +42,7 @@ describe('Deploy freeze modal', () => {
wrapper.find('#deploy-freeze-start').trigger('input');
wrapper.find('#deploy-freeze-end').trigger('input');
- wrapper.find(TimezoneDropdown).trigger('input');
+ wrapper.findComponent(TimezoneDropdown).trigger('input');
};
afterEach(() => {
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 cc044800e5e..637efe30022 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
@@ -31,11 +31,11 @@ describe('Deploy freeze settings', () => {
describe('Deploy freeze table contains components', () => {
it('contains deploy freeze table', () => {
- expect(wrapper.find(DeployFreezeTable).exists()).toBe(true);
+ expect(wrapper.findComponent(DeployFreezeTable).exists()).toBe(true);
});
it('contains deploy freeze modal', () => {
- expect(wrapper.find(DeployFreezeModal).exists()).toBe(true);
+ expect(wrapper.findComponent(DeployFreezeModal).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
index aea81daecef..567d18f8b92 100644
--- a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
+++ b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
@@ -30,8 +30,8 @@ describe('Deploy freeze timezone dropdown', () => {
wrapper.setData({ searchTerm });
};
- const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
- const findDropdownItemByIndex = (index) => wrapper.findAll(GlDropdownItem).at(index);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
afterEach(() => {
wrapper.destroy();
@@ -96,7 +96,7 @@ describe('Deploy freeze timezone dropdown', () => {
});
it('renders selected time zone as dropdown label', () => {
- expect(wrapper.find(GlDropdown).vm.text).toBe('Alaska');
+ expect(wrapper.findComponent(GlDropdown).vm.text).toBe('Alaska');
});
});
});
diff --git a/spec/frontend/deprecated_jquery_dropdown_spec.js b/spec/frontend/deprecated_jquery_dropdown_spec.js
index b18d53b317d..4a070395eaf 100644
--- a/spec/frontend/deprecated_jquery_dropdown_spec.js
+++ b/spec/frontend/deprecated_jquery_dropdown_spec.js
@@ -314,7 +314,7 @@ describe('deprecatedJQueryDropdown', () => {
});
describe('with a trackSuggestionsClickedLabel', () => {
- it('it includes data-track attributes', () => {
+ it('includes data-track attributes', () => {
const dropdown = dropdownWithOptions({
trackSuggestionClickedLabel: 'some_value_for_label',
});
@@ -333,7 +333,7 @@ describe('deprecatedJQueryDropdown', () => {
expect(link).toHaveAttr('data-track-property', 'suggestion-category');
});
- it('it defaults property to no_category when category not provided', () => {
+ it('defaults property to no_category when category not provided', () => {
const dropdown = dropdownWithOptions({
trackSuggestionClickedLabel: 'some_value_for_label',
});
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 28833b4af5c..df511586c10 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
@@ -43,6 +43,7 @@ describe('Design note component', () => {
wrapper = shallowMountExtended(DesignNote, {
propsData: {
note: {},
+ noteableId: 'gid://gitlab/DesignManagement::Design/6',
...props,
},
data() {
diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
index f7ce742b933..e36f5c79e3e 100644
--- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import Autosave from '~/autosave';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
const showModal = jest.fn();
@@ -13,6 +14,7 @@ const GlModal = {
describe('Design reply form component', () => {
let wrapper;
+ let originalGon;
const findTextarea = () => wrapper.find('textarea');
const findSubmitButton = () => wrapper.findComponent({ ref: 'submitButton' });
@@ -24,6 +26,7 @@ describe('Design reply form component', () => {
propsData: {
value: '',
isSaving: false,
+ noteableId: 'gid://gitlab/DesignManagement::Design/6',
...props,
},
stubs: { GlModal },
@@ -31,8 +34,14 @@ describe('Design reply form component', () => {
});
}
+ beforeEach(() => {
+ originalGon = window.gon;
+ window.gon.current_user_id = 1;
+ });
+
afterEach(() => {
wrapper.destroy();
+ window.gon = originalGon;
});
it('textarea has focus after component mount', () => {
@@ -66,6 +75,25 @@ describe('Design reply form component', () => {
expect(findSubmitButton().html()).toMatchSnapshot();
});
+ it.each`
+ discussionId | shortDiscussionId
+ ${undefined} | ${'new'}
+ ${'gid://gitlab/DiffDiscussion/123'} | ${123}
+ `(
+ 'initializes autosave support on discussion with proper key',
+ async ({ discussionId, shortDiscussionId }) => {
+ createComponent({ discussionId });
+ await nextTick();
+
+ // We discourage testing `wrapper.vm` properties but
+ // since `autosave` library instantiates on component
+ // there's no other way to test whether instantiation
+ // happened correctly or not.
+ expect(wrapper.vm.autosaveDiscussion).toBeInstanceOf(Autosave);
+ expect(wrapper.vm.autosaveDiscussion.key).toBe(`autosave/Discussion/6/${shortDiscussionId}`);
+ },
+ );
+
describe('when form has no text', () => {
beforeEach(() => {
createComponent({
@@ -120,28 +148,37 @@ describe('Design reply form component', () => {
});
it('emits submitForm event on Comment button click', async () => {
+ const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset');
+
findSubmitButton().vm.$emit('click');
await nextTick();
expect(wrapper.emitted('submit-form')).toBeTruthy();
+ expect(autosaveResetSpy).toHaveBeenCalled();
});
it('emits submitForm event on textarea ctrl+enter keydown', async () => {
+ const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset');
+
findTextarea().trigger('keydown.enter', {
ctrlKey: true,
});
await nextTick();
expect(wrapper.emitted('submit-form')).toBeTruthy();
+ expect(autosaveResetSpy).toHaveBeenCalled();
});
it('emits submitForm event on textarea meta+enter keydown', async () => {
+ const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset');
+
findTextarea().trigger('keydown.enter', {
metaKey: true,
});
await nextTick();
expect(wrapper.emitted('submit-form')).toBeTruthy();
+ expect(autosaveResetSpy).toHaveBeenCalled();
});
it('emits input event on changing textarea content', async () => {
@@ -180,10 +217,13 @@ describe('Design reply form component', () => {
});
it('emits cancelForm event on modal Ok button click', () => {
+ const autosaveResetSpy = jest.spyOn(wrapper.vm.autosaveDiscussion, 'reset');
+
findTextarea().trigger('keyup.esc');
findModal().vm.$emit('ok');
expect(wrapper.emitted('cancel-form')).toBeTruthy();
+ expect(autosaveResetSpy).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js
index 30eddcee86a..4a339899473 100644
--- a/spec/frontend/design_management/components/design_presentation_spec.js
+++ b/spec/frontend/design_management/components/design_presentation_spec.js
@@ -525,7 +525,7 @@ describe('Design management design presentation component', () => {
{ clientX: 10, clientY: 10 },
{ mouseup: true },
).then(() => {
- expect(wrapper.emitted('openCommentForm')).toBeFalsy();
+ expect(wrapper.emitted('openCommentForm')).toBeUndefined();
});
});
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 8fe3e92360a..096d776a7d2 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
@@ -11,7 +11,7 @@ exports[`Design management list item component when item appears in view after i
exports[`Design management list item component with notes renders item with multiple comments 1`] = `
<router-link-stub
ariacurrentvalue="page"
- class="card gl-cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ class="card gl-cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new gl-mb-0"
event="click"
tag="a"
to="[object Object]"
@@ -88,7 +88,7 @@ exports[`Design management list item component with notes renders item with mult
exports[`Design management list item component with notes renders item with single comment 1`] = `
<router-link-stub
ariacurrentvalue="page"
- class="card gl-cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ class="card gl-cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new gl-mb-0"
event="click"
tag="a"
to="[object Object]"
diff --git a/spec/frontend/design_management/components/toolbar/index_spec.js b/spec/frontend/design_management/components/toolbar/index_spec.js
index b6137ba2eee..1776405ece9 100644
--- a/spec/frontend/design_management/components/toolbar/index_spec.js
+++ b/spec/frontend/design_management/components/toolbar/index_spec.js
@@ -107,7 +107,7 @@ describe('Design management toolbar component', () => {
await nextTick();
wrapper.findComponent(DeleteButton).vm.$emit('delete-selected-designs');
- expect(wrapper.emitted().delete).toBeTruthy();
+ expect(wrapper.emitted().delete).toHaveLength(1);
});
it('renders download button with correct link', () => {
diff --git a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
index 9997f02cd01..8cfe11c9040 100644
--- a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
@@ -9,9 +9,7 @@ exports[`Design management index page designs renders error 1`] = `
<!---->
- <div
- class="gl-mt-6"
- >
+ <div>
<gl-alert-stub
dismisslabel="Dismiss"
primarybuttonlink=""
@@ -43,9 +41,7 @@ exports[`Design management index page designs renders loading icon 1`] = `
<!---->
- <div
- class="gl-mt-6"
- >
+ <div>
<gl-loading-icon-stub
color="dark"
label="Loading"
diff --git a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap
index 3177a5e016c..d86fbf81d20 100644
--- a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap
@@ -64,7 +64,7 @@ exports[`Design management design index page renders design index 1`] = `
<participants-stub
class="gl-mb-4"
lazy="true"
- numberoflessparticipants="7"
+ numberoflessparticipants="8"
participants="[object Object]"
/>
@@ -195,7 +195,7 @@ exports[`Design management design index page with error GlAlert is rendered in c
<participants-stub
class="gl-mb-4"
lazy="true"
- numberoflessparticipants="7"
+ numberoflessparticipants="8"
participants="[object Object]"
/>
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index f90feaadfb0..1033b509419 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -254,7 +254,7 @@ describe('Design management index page', () => {
'gl-flex-direction-column',
'col-md-6',
'col-lg-3',
- 'gl-mb-3',
+ 'gl-mt-5',
]);
});
});
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 96f2ac1692c..b88206c3b9a 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -30,7 +30,7 @@ const UPDATED_COMMIT_URL = `${TEST_HOST}/COMMIT/NEW`;
Vue.use(Vuex);
function getCollapsedFilesWarning(wrapper) {
- return wrapper.find(CollapsedFilesWarning);
+ return wrapper.findComponent(CollapsedFilesWarning);
}
describe('diffs/components/app', () => {
@@ -167,7 +167,7 @@ describe('diffs/components/app', () => {
state.diffs.isLoading = true;
});
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('displays loading icon on batch loading', () => {
@@ -175,13 +175,13 @@ describe('diffs/components/app', () => {
state.diffs.batchLoadingState = 'loading';
});
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('displays diffs container when not loading', () => {
createComponent();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find('#diffs').exists()).toBe(true);
});
@@ -263,7 +263,7 @@ describe('diffs/components/app', () => {
it('renders empty state when no diff files exist', () => {
createComponent();
- expect(wrapper.find(NoChanges).exists()).toBe(true);
+ expect(wrapper.findComponent(NoChanges).exists()).toBe(true);
});
it('does not render empty state when diff files exist', () => {
@@ -273,8 +273,8 @@ describe('diffs/components/app', () => {
});
});
- expect(wrapper.find(NoChanges).exists()).toBe(false);
- expect(wrapper.findAll(DiffFile).length).toBe(1);
+ expect(wrapper.findComponent(NoChanges).exists()).toBe(false);
+ expect(wrapper.findAllComponents(DiffFile).length).toBe(1);
});
});
@@ -487,8 +487,8 @@ describe('diffs/components/app', () => {
state.diffs.mergeRequestDiff = mergeRequestDiff;
});
- expect(wrapper.find(CompareVersions).exists()).toBe(true);
- expect(wrapper.find(CompareVersions).props()).toEqual(
+ expect(wrapper.findComponent(CompareVersions).exists()).toBe(true);
+ expect(wrapper.findComponent(CompareVersions).props()).toEqual(
expect.objectContaining({
diffFilesCountText: null,
}),
@@ -506,8 +506,8 @@ describe('diffs/components/app', () => {
state.diffs.size = 1;
});
- expect(wrapper.find(HiddenFilesWarning).exists()).toBe(true);
- expect(wrapper.find(HiddenFilesWarning).props()).toEqual(
+ expect(wrapper.findComponent(HiddenFilesWarning).exists()).toBe(true);
+ expect(wrapper.findComponent(HiddenFilesWarning).props()).toEqual(
expect.objectContaining({
total: '5',
plainDiffPath: 'plain diff path',
@@ -547,7 +547,7 @@ describe('diffs/components/app', () => {
};
});
- expect(wrapper.find(CommitWidget).exists()).toBe(true);
+ expect(wrapper.findComponent(CommitWidget).exists()).toBe(true);
});
it('should display diff file if there are diff files', () => {
@@ -555,13 +555,13 @@ describe('diffs/components/app', () => {
state.diffs.diffFiles.push({ sha: '123' });
});
- expect(wrapper.find(DiffFile).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffFile).exists()).toBe(true);
});
it("doesn't render tree list when no changes exist", () => {
createComponent();
- expect(wrapper.find(TreeList).exists()).toBe(false);
+ expect(wrapper.findComponent(TreeList).exists()).toBe(false);
});
it('should render tree list', () => {
@@ -569,7 +569,7 @@ describe('diffs/components/app', () => {
state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
});
- expect(wrapper.find(TreeList).exists()).toBe(true);
+ expect(wrapper.findComponent(TreeList).exists()).toBe(true);
});
});
@@ -636,12 +636,12 @@ describe('diffs/components/app', () => {
await nextTick();
- expect(wrapper.findAll(DiffFile).length).toBe(1);
+ expect(wrapper.findAllComponents(DiffFile).length).toBe(1);
});
describe('pagination', () => {
const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]');
- const paginator = () => fileByFileNav().find(GlPagination);
+ const paginator = () => fileByFileNav().findComponent(GlPagination);
it('sets previous button as disabled', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
@@ -682,7 +682,7 @@ describe('diffs/components/app', () => {
${'123'} | ${2}
${'312'} | ${1}
`(
- 'it calls navigateToDiffFileIndex with $index when $link is clicked',
+ 'calls navigateToDiffFileIndex with $index when $link is clicked',
async ({ currentDiffFileId, targetFile }) => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
index cc4f13ab0cf..eca5b536a35 100644
--- a/spec/frontend/diffs/components/collapsed_files_warning_spec.js
+++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
@@ -28,8 +28,8 @@ describe('CollapsedFilesWarning', () => {
Vue.use(Vuex);
const getAlertActionButton = () =>
- wrapper.find(CollapsedFilesWarning).find('button.gl-alert-action:first-child');
- const getAlertCloseButton = () => wrapper.find(CollapsedFilesWarning).find('button');
+ wrapper.findComponent(CollapsedFilesWarning).find('button.gl-alert-action:first-child');
+ const getAlertCloseButton = () => wrapper.findComponent(CollapsedFilesWarning).find('button');
const createComponent = (props = {}, { full } = { full: false }) => {
const mounter = full ? mount : shallowMount;
diff --git a/spec/frontend/diffs/components/commit_item_spec.js b/spec/frontend/diffs/components/commit_item_spec.js
index e52c5abbc7b..440f169be86 100644
--- a/spec/frontend/diffs/components/commit_item_spec.js
+++ b/spec/frontend/diffs/components/commit_item_spec.js
@@ -27,7 +27,7 @@ describe('diffs/components/commit_item', () => {
const getAvatarElement = () => wrapper.find('.user-avatar-link');
const getCommitterElement = () => wrapper.find('.committer');
const getCommitActionsElement = () => wrapper.find('.commit-actions');
- const getCommitPipelineStatus = () => wrapper.find(CommitPipelineStatus);
+ const getCommitPipelineStatus = () => wrapper.findComponent(CommitPipelineStatus);
const mountComponent = (propsData) => {
wrapper = mount(Component, {
@@ -111,8 +111,8 @@ describe('diffs/components/commit_item', () => {
const descElement = getDescElement();
const descExpandElement = getDescExpandElement();
- expect(descElement.exists()).toBeFalsy();
- expect(descExpandElement.exists()).toBeFalsy();
+ expect(descElement.exists()).toBe(false);
+ expect(descExpandElement.exists()).toBe(false);
});
});
diff --git a/spec/frontend/diffs/components/commit_widget_spec.js b/spec/frontend/diffs/components/commit_widget_spec.js
index fbff473e4df..f650ead6f83 100644
--- a/spec/frontend/diffs/components/commit_widget_spec.js
+++ b/spec/frontend/diffs/components/commit_widget_spec.js
@@ -12,7 +12,7 @@ describe('diffs/components/commit_widget', () => {
});
it('renders commit item', () => {
- const commitElement = wrapper.find(CommitItem);
+ const commitElement = wrapper.findComponent(CommitItem);
expect(commitElement.exists()).toBe(true);
});
diff --git a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
index 98f88226742..09128b04caa 100644
--- a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
+++ b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
@@ -34,7 +34,7 @@ describe('CompareDropdownLayout', () => {
findListItems().wrappers.map((listItem) => ({
href: listItem.find('a').attributes('href'),
text: trimText(listItem.text()),
- createdAt: listItem.findAll(TimeAgo).wrappers[0]?.props('time'),
+ createdAt: listItem.findAllComponents(TimeAgo).wrappers[0]?.props('time'),
isActive: listItem.classes().includes('is-active'),
}));
diff --git a/spec/frontend/diffs/components/diff_code_quality_spec.js b/spec/frontend/diffs/components/diff_code_quality_spec.js
index 81a817c47dc..b5dce4fc924 100644
--- a/spec/frontend/diffs/components/diff_code_quality_spec.js
+++ b/spec/frontend/diffs/components/diff_code_quality_spec.js
@@ -17,7 +17,6 @@ describe('DiffCodeQuality', () => {
return mountFunction(DiffCodeQuality, {
propsData: {
expandedLines: [],
- line: 1,
codeQuality,
},
});
@@ -28,9 +27,7 @@ describe('DiffCodeQuality', () => {
expect(wrapper.findByTestId('diff-codequality').exists()).toBe(true);
await wrapper.findByTestId('diff-codequality-close').trigger('click');
-
expect(wrapper.emitted('hideCodeQualityFindings').length).toBe(1);
- expect(wrapper.emitted().hideCodeQualityFindings[0][0]).toBe(wrapper.props('line'));
});
it('renders correct amount of list items for codequality array and their description', async () => {
diff --git a/spec/frontend/diffs/components/diff_comment_cell_spec.js b/spec/frontend/diffs/components/diff_comment_cell_spec.js
index b636a178593..2acfc2c6d7e 100644
--- a/spec/frontend/diffs/components/diff_comment_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_comment_cell_spec.js
@@ -20,24 +20,24 @@ describe('DiffCommentCell', () => {
it('renders discussions if line has discussions', () => {
const wrapper = createWrapper({ renderDiscussion: true });
- expect(wrapper.find(DiffDiscussions).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffDiscussions).exists()).toBe(true);
});
it('does not render discussions if line has no discussions', () => {
const wrapper = createWrapper();
- expect(wrapper.find(DiffDiscussions).exists()).toBe(false);
+ expect(wrapper.findComponent(DiffDiscussions).exists()).toBe(false);
});
it('renders discussion reply if line has no draft', () => {
const wrapper = createWrapper();
- expect(wrapper.find(DiffDiscussionReply).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffDiscussionReply).exists()).toBe(true);
});
it('does not render discussion reply if line has draft', () => {
const wrapper = createWrapper({ hasDraft: true });
- expect(wrapper.find(DiffDiscussionReply).exists()).toBe(false);
+ expect(wrapper.findComponent(DiffDiscussionReply).exists()).toBe(false);
});
});
diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js
index 6844e6e497a..9f593ee0d49 100644
--- a/spec/frontend/diffs/components/diff_content_spec.js
+++ b/spec/frontend/diffs/components/diff_content_spec.js
@@ -110,13 +110,13 @@ describe('DiffContent', () => {
props: { diffFile: textDiffFile },
});
- expect(wrapper.find(DiffView).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffView).exists()).toBe(true);
});
it('renders rendering more lines loading icon', () => {
createComponent({ props: { diffFile: { ...textDiffFile, renderingLines: true } } });
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
@@ -133,7 +133,7 @@ describe('DiffContent', () => {
props: { diffFile: { ...emptyDiffFile, viewer: { name: diffViewerModes.no_preview } } },
});
- expect(wrapper.find(NoPreviewViewer).exists()).toBe(true);
+ expect(wrapper.findComponent(NoPreviewViewer).exists()).toBe(true);
});
it('should render not diffable view if viewer set to non_diffable', () => {
@@ -141,7 +141,7 @@ describe('DiffContent', () => {
props: { diffFile: { ...emptyDiffFile, viewer: { name: diffViewerModes.not_diffable } } },
});
- expect(wrapper.find(NotDiffableViewer).exists()).toBe(true);
+ expect(wrapper.findComponent(NotDiffableViewer).exists()).toBe(true);
});
});
@@ -156,7 +156,7 @@ describe('DiffContent', () => {
},
});
- expect(wrapper.find(DiffDiscussions).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffDiscussions).exists()).toBe(true);
});
it('emits saveDiffDiscussion when note-form emits `handleFormUpdate`', () => {
@@ -169,7 +169,7 @@ describe('DiffContent', () => {
},
});
- wrapper.find(NoteForm).vm.$emit('handleFormUpdate', noteStub);
+ wrapper.findComponent(NoteForm).vm.$emit('handleFormUpdate', noteStub);
expect(saveDiffDiscussionMock).toHaveBeenCalledWith(expect.any(Object), {
note: noteStub,
formData: {
diff --git a/spec/frontend/diffs/components/diff_discussion_reply_spec.js b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
index f03c0357a0e..5ccd2002462 100644
--- a/spec/frontend/diffs/components/diff_discussion_reply_spec.js
+++ b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
@@ -64,7 +64,7 @@ describe('DiffDiscussionReply', () => {
hasForm: false,
});
- expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
+ expect(wrapper.findComponent(ReplyPlaceholder).exists()).toBe(true);
});
});
@@ -83,6 +83,6 @@ describe('DiffDiscussionReply', () => {
hasForm: false,
});
- expect(wrapper.find(NoteSignedOutWidget).exists()).toBe(true);
+ expect(wrapper.findComponent(NoteSignedOutWidget).exists()).toBe(true);
});
});
diff --git a/spec/frontend/diffs/components/diff_discussions_spec.js b/spec/frontend/diffs/components/diff_discussions_spec.js
index 2da68adddf6..e9a0e0745fd 100644
--- a/spec/frontend/diffs/components/diff_discussions_spec.js
+++ b/spec/frontend/diffs/components/diff_discussions_spec.js
@@ -32,11 +32,11 @@ describe('DiffDiscussions', () => {
it('should have notes list', () => {
createComponent();
- expect(wrapper.find(NoteableDiscussion).exists()).toBe(true);
- expect(wrapper.find(DiscussionNotes).exists()).toBe(true);
- expect(wrapper.find(DiscussionNotes).findAll(TimelineEntryItem).length).toBe(
- discussionsMockData.notes.length,
- );
+ expect(wrapper.findComponent(NoteableDiscussion).exists()).toBe(true);
+ expect(wrapper.findComponent(DiscussionNotes).exists()).toBe(true);
+ expect(
+ wrapper.findComponent(DiscussionNotes).findAllComponents(TimelineEntryItem).length,
+ ).toBe(discussionsMockData.notes.length);
});
});
@@ -48,7 +48,7 @@ describe('DiffDiscussions', () => {
const diffNotesToggle = findDiffNotesToggle();
expect(diffNotesToggle.exists()).toBe(true);
- expect(diffNotesToggle.find(GlIcon).exists()).toBe(true);
+ expect(diffNotesToggle.findComponent(GlIcon).exists()).toBe(true);
expect(diffNotesToggle.classes('diff-notes-collapse')).toBe(true);
});
@@ -80,12 +80,12 @@ describe('DiffDiscussions', () => {
discussions[0].expanded = false;
createComponent({ discussions, shouldCollapseDiscussions: true });
- expect(wrapper.find(NoteableDiscussion).isVisible()).toBe(false);
+ expect(wrapper.findComponent(NoteableDiscussion).isVisible()).toBe(false);
});
it('renders badge on avatar', () => {
createComponent({ renderAvatarBadge: true });
- const noteableDiscussion = wrapper.find(NoteableDiscussion);
+ const noteableDiscussion = wrapper.findComponent(NoteableDiscussion);
expect(noteableDiscussion.find('.design-note-pin').exists()).toBe(true);
expect(noteableDiscussion.find('.design-note-pin').text().trim()).toBe('1');
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index 92b8b2d4aa3..c23eb2f3d24 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -76,18 +76,19 @@ describe('DiffFileHeader component', () => {
wrapper.destroy();
});
- const findHeader = () => wrapper.find({ ref: 'header' });
- const findTitleLink = () => wrapper.find({ ref: 'titleWrapper' });
- const findExpandButton = () => wrapper.find({ ref: 'expandDiffToFullFileButton' });
+ const findHeader = () => wrapper.findComponent({ ref: 'header' });
+ const findTitleLink = () => wrapper.findComponent({ ref: 'titleWrapper' });
+ const findExpandButton = () => wrapper.findComponent({ ref: 'expandDiffToFullFileButton' });
const findFileActions = () => wrapper.find('.file-actions');
- const findModeChangedLine = () => wrapper.find({ ref: 'fileMode' });
+ const findModeChangedLine = () => wrapper.findComponent({ ref: 'fileMode' });
const findLfsLabel = () => wrapper.find('[data-testid="label-lfs"]');
- const findToggleDiscussionsButton = () => wrapper.find({ ref: 'toggleDiscussionsButton' });
- const findExternalLink = () => wrapper.find({ ref: 'externalLink' });
- const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' });
- const findViewFileButton = () => wrapper.find({ ref: 'viewButton' });
- const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' });
- const findEditButton = () => wrapper.find({ ref: 'editButton' });
+ const findToggleDiscussionsButton = () =>
+ wrapper.findComponent({ ref: 'toggleDiscussionsButton' });
+ const findExternalLink = () => wrapper.findComponent({ ref: 'externalLink' });
+ const findReplacedFileButton = () => wrapper.findComponent({ ref: 'replacedFileButton' });
+ const findViewFileButton = () => wrapper.findComponent({ ref: 'viewButton' });
+ const findCollapseIcon = () => wrapper.findComponent({ ref: 'collapseIcon' });
+ const findEditButton = () => wrapper.findComponent({ ref: 'editButton' });
const findReviewFileCheckbox = () => wrapper.find("[data-testid='fileReviewCheckbox']");
const createComponent = ({ props, options = {} } = {}) => {
@@ -153,7 +154,7 @@ describe('DiffFileHeader component', () => {
});
it('displays a copy to clipboard button', () => {
- expect(wrapper.find(ClipboardButton).exists()).toBe(true);
+ expect(wrapper.findComponent(ClipboardButton).exists()).toBe(true);
});
it('triggers the copy to clipboard tracking event', () => {
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index 1d1c5fec293..c5b76551fcc 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -32,7 +32,7 @@ describe('Diff File Row component', () => {
...diffFileRowProps,
});
- expect(wrapper.find(FileRow).props()).toEqual(
+ expect(wrapper.findComponent(FileRow).props()).toEqual(
expect.objectContaining({
...sharedProps,
}),
@@ -47,7 +47,7 @@ describe('Diff File Row component', () => {
showTooltip: true,
});
- expect(wrapper.find(ChangedFileIcon).props()).toEqual(
+ expect(wrapper.findComponent(ChangedFileIcon).props()).toEqual(
expect.objectContaining({
file: {},
size: 16,
@@ -74,7 +74,7 @@ describe('Diff File Row component', () => {
hideFileStats: false,
viewedFiles: isViewed ? { '#123456789': true } : {},
});
- expect(wrapper.find(FileRow).props('fileClasses')).toBe(expected);
+ expect(wrapper.findComponent(FileRow).props('fileClasses')).toBe(expected);
},
);
@@ -92,7 +92,7 @@ describe('Diff File Row component', () => {
},
hideFileStats,
});
- expect(wrapper.find(FileRowStats).exists()).toEqual(value);
+ expect(wrapper.findComponent(FileRowStats).exists()).toEqual(value);
});
});
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index 9e8d9e1ca29..944cec77efb 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -100,7 +100,7 @@ function createComponent({ file, first = false, last = false, options = {}, prop
};
}
-const findDiffHeader = (wrapper) => wrapper.find(DiffFileHeaderComponent);
+const findDiffHeader = (wrapper) => wrapper.findComponent(DiffFileHeaderComponent);
const findDiffContentArea = (wrapper) => wrapper.find('[data-testid="content-area"]');
const findLoader = (wrapper) => wrapper.find('[data-testid="loader-icon"]');
const findToggleButton = (wrapper) => wrapper.find('[data-testid="expand-button"]');
@@ -209,14 +209,14 @@ describe('DiffFile', () => {
expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0);
expect(el.querySelector('.js-file-title')).toBeDefined();
- expect(wrapper.find(DiffFileHeaderComponent).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffFileHeaderComponent).exists()).toBe(true);
expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
markFileToBeRendered(store);
await nextTick();
- expect(wrapper.find(DiffContentComponent).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffContentComponent).exists()).toBe(true);
});
});
@@ -320,7 +320,7 @@ describe('DiffFile', () => {
});
it('should have the file content', async () => {
- expect(wrapper.find(DiffContentComponent).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffContentComponent).exists()).toBe(true);
});
it('should style the component so that it `.has-body` for layout purposes', () => {
@@ -473,8 +473,8 @@ describe('DiffFile', () => {
await nextTick();
expect(wrapper.classes('has-body')).toBe(true);
- expect(wrapper.find(DiffContentComponent).exists()).toBe(true);
- expect(wrapper.find(DiffContentComponent).isVisible()).toBe(true);
+ expect(wrapper.findComponent(DiffContentComponent).exists()).toBe(true);
+ expect(wrapper.findComponent(DiffContentComponent).isVisible()).toBe(true);
},
);
});
diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
index c18f0b721da..f13988fc11f 100644
--- a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
+++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
+import { HIDE_COMMENTS } from '~/diffs/i18n';
import discussionsMockData from '../mock_data/diff_discussions';
const getDiscussionsMockData = () => [{ ...discussionsMockData }];
@@ -40,7 +41,12 @@ describe('DiffGutterAvatars', () => {
findCollapseButton().trigger('click');
await nextTick();
- expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
+ expect(wrapper.emitted().toggleLineDiscussions).toBeDefined();
+ });
+
+ it('renders the proper title and aria-label', () => {
+ expect(findCollapseButton().attributes('title')).toBe(HIDE_COMMENTS);
+ expect(findCollapseButton().attributes('aria-label')).toBe(HIDE_COMMENTS);
});
});
@@ -69,14 +75,14 @@ describe('DiffGutterAvatars', () => {
findUserAvatars().at(0).trigger('click');
await nextTick();
- expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
+ expect(wrapper.emitted().toggleLineDiscussions).toBeDefined();
});
it('should emit toggleDiscussions event on more count text click', async () => {
findMoreCount().trigger('click');
await nextTick();
- expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
+ expect(wrapper.emitted().toggleLineDiscussions).toBeDefined();
});
});
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 542d61c4680..9493dc8855e 100644
--- a/spec/frontend/diffs/components/diff_line_note_form_spec.js
+++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js
@@ -82,7 +82,7 @@ describe('DiffLineNoteForm', () => {
});
it('shows note form', () => {
- expect(wrapper.find(NoteForm).exists()).toBe(true);
+ expect(wrapper.findComponent(NoteForm).exists()).toBe(true);
});
it('passes the provided range of lines to comment form', () => {
diff --git a/spec/frontend/diffs/components/diff_line_spec.js b/spec/frontend/diffs/components/diff_line_spec.js
new file mode 100644
index 00000000000..37368eb1461
--- /dev/null
+++ b/spec/frontend/diffs/components/diff_line_spec.js
@@ -0,0 +1,65 @@
+import { shallowMount } from '@vue/test-utils';
+import DiffLine from '~/diffs/components/diff_line.vue';
+import DiffCodeQuality from '~/diffs/components/diff_code_quality.vue';
+
+const EXAMPLE_LINE_NUMBER = 3;
+const EXAMPLE_DESCRIPTION = 'example description';
+const EXAMPLE_SEVERITY = 'example severity';
+
+const left = {
+ line: {
+ left: {
+ codequality: [
+ {
+ line: EXAMPLE_LINE_NUMBER,
+ description: EXAMPLE_DESCRIPTION,
+ severity: EXAMPLE_SEVERITY,
+ },
+ ],
+ },
+ },
+};
+
+const right = {
+ line: {
+ right: {
+ codequality: [
+ {
+ line: EXAMPLE_LINE_NUMBER,
+ description: EXAMPLE_DESCRIPTION,
+ severity: EXAMPLE_SEVERITY,
+ },
+ ],
+ },
+ },
+};
+
+const mockData = [right, left];
+
+describe('DiffLine', () => {
+ const createWrapper = (propsData) => {
+ return shallowMount(DiffLine, { propsData });
+ };
+
+ it('should emit event when hideCodeQualityFindings is called', () => {
+ const wrapper = createWrapper(right);
+
+ wrapper.findComponent(DiffCodeQuality).vm.$emit('hideCodeQualityFindings');
+ expect(wrapper.emitted()).toEqual({
+ hideCodeQualityFindings: [[EXAMPLE_LINE_NUMBER]],
+ });
+ });
+
+ mockData.forEach((element) => {
+ it('should set correct props for DiffCodeQuality', () => {
+ const wrapper = createWrapper(element);
+ expect(wrapper.findComponent(DiffCodeQuality).props('codeQuality')).toEqual([
+ {
+ line: EXAMPLE_LINE_NUMBER,
+ description: EXAMPLE_DESCRIPTION,
+ severity: EXAMPLE_SEVERITY,
+ },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js
index 09fe69e97de..3a04547fa69 100644
--- a/spec/frontend/diffs/components/diff_stats_spec.js
+++ b/spec/frontend/diffs/components/diff_stats_spec.js
@@ -87,7 +87,7 @@ describe('diff_stats', () => {
describe('files changes', () => {
const findIcon = (name) =>
wrapper
- .findAll(GlIcon)
+ .findAllComponents(GlIcon)
.filter((c) => c.attributes('name') === name)
.at(0).element.parentNode;
diff --git a/spec/frontend/diffs/components/diff_view_spec.js b/spec/frontend/diffs/components/diff_view_spec.js
index 15923a1c6de..1dd4a2f6c23 100644
--- a/spec/frontend/diffs/components/diff_view_spec.js
+++ b/spec/frontend/diffs/components/diff_view_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DiffView from '~/diffs/components/diff_view.vue';
-import DiffCodeQuality from '~/diffs/components/diff_code_quality.vue';
+import DiffLine from '~/diffs/components/diff_line.vue';
import { diffCodeQuality } from '../mock_data/diff_code_quality';
describe('DiffView', () => {
@@ -51,28 +51,27 @@ describe('DiffView', () => {
return shallowMount(DiffView, { propsData, store, stubs, provide });
};
- it('does not render a codeQuality diff view when there is no finding', () => {
+ it('does not render a diff-line component when there is no finding', () => {
const wrapper = createWrapper();
- expect(wrapper.findComponent(DiffCodeQuality).exists()).toBe(false);
+ expect(wrapper.findComponent(DiffLine).exists()).toBe(false);
});
- it('does render a codeQuality diff view with the correct props when there is a finding & refactorCodeQualityInlineFindings flag is true ', async () => {
+ it('does render a diff-line component with the correct props when there is a finding & refactorCodeQualityInlineFindings flag is true', async () => {
const wrapper = createWrapper(diffCodeQuality, {
glFeatures: { refactorCodeQualityInlineFindings: true },
});
wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2);
await nextTick();
- expect(wrapper.findComponent(DiffCodeQuality).exists()).toBe(true);
- expect(wrapper.findComponent(DiffCodeQuality).props().codeQuality.length).not.toBe(0);
+ expect(wrapper.findComponent(DiffLine).props('line')).toBe(diffCodeQuality.diffLines[2]);
});
- it('does not render a codeQuality diff view when there is a finding & refactorCodeQualityInlineFindings flag is false ', async () => {
+ it('does not render a diff-line component when there is a finding & refactorCodeQualityInlineFindings flag is false', async () => {
const wrapper = createWrapper(diffCodeQuality, {
glFeatures: { refactorCodeQualityInlineFindings: false },
});
wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2);
await nextTick();
- expect(wrapper.findComponent(DiffCodeQuality).exists()).toBe(false);
+ expect(wrapper.findComponent(DiffLine).exists()).toBe(false);
});
it.each`
@@ -89,8 +88,8 @@ describe('DiffView', () => {
diffLines: [{ renderCommentRow: true, ...sides }],
inline: type === 'inline',
});
- expect(wrapper.findAll(DiffCommentCell).length).toBe(total);
- expect(wrapper.find(container).find(DiffCommentCell).exists()).toBe(true);
+ expect(wrapper.findAllComponents(DiffCommentCell).length).toBe(total);
+ expect(wrapper.find(container).findComponent(DiffCommentCell).exists()).toBe(true);
},
);
@@ -98,7 +97,7 @@ describe('DiffView', () => {
const wrapper = createWrapper({
diffLines: [{ renderCommentRow: true, left: { lineDraft: { isDraft: true } } }],
});
- expect(wrapper.find(DraftNote).exists()).toBe(true);
+ expect(wrapper.findComponent(DraftNote).exists()).toBe(true);
});
describe('drag operations', () => {
diff --git a/spec/frontend/diffs/components/image_diff_overlay_spec.js b/spec/frontend/diffs/components/image_diff_overlay_spec.js
index 70191620eb6..ccf942bdcef 100644
--- a/spec/frontend/diffs/components/image_diff_overlay_spec.js
+++ b/spec/frontend/diffs/components/image_diff_overlay_spec.js
@@ -57,7 +57,7 @@ describe('Diffs image diff overlay component', () => {
it('renders icon when showCommentIcon is true', () => {
createComponent({ showCommentIcon: true });
- expect(wrapper.find(GlIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlIcon).exists()).toBe(true);
});
it('sets badge comment positions', () => {
diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index 6903b844e5e..dbfe9770e07 100644
--- a/spec/frontend/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
@@ -56,7 +56,7 @@ describe('Diff no changes empty state', () => {
it('Show create commit button', () => {
createComponent();
- expect(wrapper.find(GlButton).exists()).toBe(true);
+ expect(wrapper.findComponent(GlButton).exists()).toBe(true);
});
it.each`
diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js
index 931a9562d36..ca7de8fd751 100644
--- a/spec/frontend/diffs/components/tree_list_spec.js
+++ b/spec/frontend/diffs/components/tree_list_spec.js
@@ -106,7 +106,7 @@ describe('Diffs tree list component', () => {
${'index.js'} | ${1}
${'app/*.js'} | ${1}
${'*.js, *.rb'} | ${2}
- `('it returns $itemSize item for $extension', async ({ extension, itemSize }) => {
+ `('returns $itemSize item for $extension', async ({ extension, itemSize }) => {
wrapper.find('[data-testid="diff-tree-search"]').setValue(extension);
await nextTick();
@@ -175,7 +175,7 @@ describe('Diffs tree list component', () => {
await nextTick();
// Have to use $attrs['viewed-files'] because we are passing down an object
// and attributes('') stringifies values (e.g. [object])...
- expect(wrapper.find(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds);
+ expect(wrapper.findComponent(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds);
});
});
});
diff --git a/spec/frontend/editor/components/source_editor_toolbar_spec.js b/spec/frontend/editor/components/source_editor_toolbar_spec.js
index 6e99eadbd97..bead39ca744 100644
--- a/spec/frontend/editor/components/source_editor_toolbar_spec.js
+++ b/spec/frontend/editor/components/source_editor_toolbar_spec.js
@@ -68,7 +68,7 @@ describe('Source Editor Toolbar', () => {
});
describe('buttons update', () => {
- it('it properly updates buttons on Apollo cache update', async () => {
+ it('properly updates buttons on Apollo cache update', async () => {
const item = buildButton('first', {
group: EDITOR_TOOLBAR_RIGHT_GROUP,
});
diff --git a/spec/frontend/editor/source_editor_extension_spec.js b/spec/frontend/editor/source_editor_extension_spec.js
index 78453aaa491..3424e71d326 100644
--- a/spec/frontend/editor/source_editor_extension_spec.js
+++ b/spec/frontend/editor/source_editor_extension_spec.js
@@ -16,7 +16,7 @@ describe('Editor Extension', () => {
'throws when definition = $definition and setupOptions = $setupOptions',
({ definition, setupOptions }) => {
const constructExtension = () => new EditorExtension({ definition, setupOptions });
- expect(constructExtension).toThrowError(EDITOR_EXTENSION_DEFINITION_ERROR);
+ expect(constructExtension).toThrow(EDITOR_EXTENSION_DEFINITION_ERROR);
},
);
diff --git a/spec/frontend/editor/source_editor_instance_spec.js b/spec/frontend/editor/source_editor_instance_spec.js
index 1223fee320e..20ba23d56ff 100644
--- a/spec/frontend/editor/source_editor_instance_spec.js
+++ b/spec/frontend/editor/source_editor_instance_spec.js
@@ -248,7 +248,7 @@ describe('Source Editor Instance', () => {
const useExtension = () => {
seInstance.use(extensions);
};
- expect(useExtension).toThrowError(thrownError);
+ expect(useExtension).toThrow(thrownError);
},
);
@@ -336,7 +336,7 @@ describe('Source Editor Instance', () => {
const unuse = () => {
seInstance.unuse(unuseExtension);
};
- expect(unuse).toThrowError(thrownError);
+ expect(unuse).toThrow(thrownError);
},
);
@@ -382,7 +382,7 @@ describe('Source Editor Instance', () => {
},
);
- it('it does not remove entry from the global registry to keep for potential future re-use', () => {
+ it('does not remove entry from the global registry to keep for potential future re-use', () => {
const extensionStore = new Map();
seInstance = new SourceEditorInstance({}, extensionStore);
const extensions = seInstance.use(fullExtensionsArray);
diff --git a/spec/frontend/editor/source_editor_webide_ext_spec.js b/spec/frontend/editor/source_editor_webide_ext_spec.js
index 096b6b1646f..f418eab668a 100644
--- a/spec/frontend/editor/source_editor_webide_ext_spec.js
+++ b/spec/frontend/editor/source_editor_webide_ext_spec.js
@@ -30,7 +30,7 @@ describe('Source Editor Web IDE Extension', () => {
const sideBySideSpy = jest.spyOn(instance, 'updateOptions');
instance.use({ definition: EditorWebIdeExtension });
- expect(sideBySideSpy).toBeCalledWith({ renderSideBySide });
+ expect(sideBySideSpy).toHaveBeenCalledWith({ renderSideBySide });
},
);
@@ -45,11 +45,11 @@ describe('Source Editor Web IDE Extension', () => {
const sideBySideSpy = jest.spyOn(instance, 'updateOptions');
await emitter.fire();
- expect(sideBySideSpy).toBeCalledWith({ renderSideBySide: true });
+ expect(sideBySideSpy).toHaveBeenCalledWith({ renderSideBySide: true });
editorEl.style.width = '0px';
await emitter.fire();
- expect(sideBySideSpy).toBeCalledWith({ renderSideBySide: false });
+ expect(sideBySideSpy).toHaveBeenCalledWith({ renderSideBySide: false });
});
});
});
diff --git a/spec/frontend/emoji/components/category_spec.js b/spec/frontend/emoji/components/category_spec.js
index 82dc0cdc250..90816f28d5b 100644
--- a/spec/frontend/emoji/components/category_spec.js
+++ b/spec/frontend/emoji/components/category_spec.js
@@ -22,7 +22,7 @@ describe('Emoji category component', () => {
});
it('renders emoji groups', () => {
- expect(wrapper.findAll(EmojiGroup).length).toBe(2);
+ expect(wrapper.findAllComponents(EmojiGroup).length).toBe(2);
});
it('renders group', async () => {
@@ -30,19 +30,19 @@ describe('Emoji category component', () => {
// eslint-disable-next-line no-restricted-syntax
await wrapper.setData({ renderGroup: true });
- expect(wrapper.find(EmojiGroup).attributes('rendergroup')).toBe('true');
+ expect(wrapper.findComponent(EmojiGroup).attributes('rendergroup')).toBe('true');
});
it('renders group on appear', async () => {
- wrapper.find(GlIntersectionObserver).vm.$emit('appear');
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
await nextTick();
- expect(wrapper.find(EmojiGroup).attributes('rendergroup')).toBe('true');
+ expect(wrapper.findComponent(EmojiGroup).attributes('rendergroup')).toBe('true');
});
it('emits appear event on appear', async () => {
- wrapper.find(GlIntersectionObserver).vm.$emit('appear');
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
await nextTick();
diff --git a/spec/frontend/emoji/components/utils_spec.js b/spec/frontend/emoji/components/utils_spec.js
index 56f514ee9a8..a17ddb3bb9a 100644
--- a/spec/frontend/emoji/components/utils_spec.js
+++ b/spec/frontend/emoji/components/utils_spec.js
@@ -4,13 +4,13 @@ import { getFrequentlyUsedEmojis, addToFrequentlyUsed } from '~/emoji/components
jest.mock('~/lib/utils/cookies');
describe('getFrequentlyUsedEmojis', () => {
- it('it returns null when no saved emojis set', () => {
+ it('returns null when no saved emojis set', () => {
jest.spyOn(Cookies, 'get').mockReturnValue(null);
expect(getFrequentlyUsedEmojis()).toBe(null);
});
- it('it returns frequently used emojis object', () => {
+ it('returns frequently used emojis object', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsup,thumbsdown');
expect(getFrequentlyUsedEmojis()).toEqual({
diff --git a/spec/frontend/emoji/index_spec.js b/spec/frontend/emoji/index_spec.js
index dc8f50e0e4b..36c3eeb5a52 100644
--- a/spec/frontend/emoji/index_spec.js
+++ b/spec/frontend/emoji/index_spec.js
@@ -120,177 +120,177 @@ describe('emoji', () => {
describe('isFlagEmoji', () => {
it('should gracefully handle empty string', () => {
- expect(isFlagEmoji('')).toBeFalsy();
+ expect(isFlagEmoji('')).toBe(false);
});
it('should detect flag_ac', () => {
- expect(isFlagEmoji('🇦🇨')).toBeTruthy();
+ expect(isFlagEmoji('🇦🇨')).toBe(true);
});
it('should detect flag_us', () => {
- expect(isFlagEmoji('🇺🇸')).toBeTruthy();
+ expect(isFlagEmoji('🇺🇸')).toBe(true);
});
it('should detect flag_zw', () => {
- expect(isFlagEmoji('🇿🇼')).toBeTruthy();
+ expect(isFlagEmoji('🇿🇼')).toBe(true);
});
it('should not detect flags', () => {
- expect(isFlagEmoji('🎏')).toBeFalsy();
+ expect(isFlagEmoji('🎏')).toBe(false);
});
it('should not detect triangular_flag_on_post', () => {
- expect(isFlagEmoji('🚩')).toBeFalsy();
+ expect(isFlagEmoji('🚩')).toBe(false);
});
it('should not detect single letter', () => {
- expect(isFlagEmoji('🇦')).toBeFalsy();
+ expect(isFlagEmoji('🇦')).toBe(false);
});
it('should not detect >2 letters', () => {
- expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy();
+ expect(isFlagEmoji('🇦🇧🇨')).toBe(false);
});
});
describe('isRainbowFlagEmoji', () => {
it('should gracefully handle empty string', () => {
- expect(isRainbowFlagEmoji('')).toBeFalsy();
+ expect(isRainbowFlagEmoji('')).toBe(false);
});
it('should detect rainbow_flag', () => {
- expect(isRainbowFlagEmoji('🏳🌈')).toBeTruthy();
+ expect(isRainbowFlagEmoji('🏳🌈')).toBe(true);
});
it("should not detect flag_white on its' own", () => {
- expect(isRainbowFlagEmoji('🏳')).toBeFalsy();
+ expect(isRainbowFlagEmoji('🏳')).toBe(false);
});
it("should not detect rainbow on its' own", () => {
- expect(isRainbowFlagEmoji('🌈')).toBeFalsy();
+ expect(isRainbowFlagEmoji('🌈')).toBe(false);
});
it('should not detect flag_white with something else', () => {
- expect(isRainbowFlagEmoji('🏳🔵')).toBeFalsy();
+ expect(isRainbowFlagEmoji('🏳🔵')).toBe(false);
});
});
describe('isKeycapEmoji', () => {
it('should gracefully handle empty string', () => {
- expect(isKeycapEmoji('')).toBeFalsy();
+ expect(isKeycapEmoji('')).toBe(false);
});
it('should detect one(keycap)', () => {
- expect(isKeycapEmoji('1️⃣')).toBeTruthy();
+ expect(isKeycapEmoji('1️⃣')).toBe(true);
});
it('should detect nine(keycap)', () => {
- expect(isKeycapEmoji('9️⃣')).toBeTruthy();
+ expect(isKeycapEmoji('9️⃣')).toBe(true);
});
it('should not detect ten(keycap)', () => {
- expect(isKeycapEmoji('🔟')).toBeFalsy();
+ expect(isKeycapEmoji('🔟')).toBe(false);
});
it('should not detect hash(keycap)', () => {
- expect(isKeycapEmoji('#⃣')).toBeFalsy();
+ expect(isKeycapEmoji('#⃣')).toBe(false);
});
});
describe('isSkinToneComboEmoji', () => {
it('should gracefully handle empty string', () => {
- expect(isSkinToneComboEmoji('')).toBeFalsy();
+ expect(isSkinToneComboEmoji('')).toBe(false);
});
it('should detect hand_splayed_tone5', () => {
- expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy();
+ expect(isSkinToneComboEmoji('🖐🏿')).toBe(true);
});
it('should not detect hand_splayed', () => {
- expect(isSkinToneComboEmoji('🖐')).toBeFalsy();
+ expect(isSkinToneComboEmoji('🖐')).toBe(false);
});
it('should detect lifter_tone1', () => {
- expect(isSkinToneComboEmoji('🏋🏻')).toBeTruthy();
+ expect(isSkinToneComboEmoji('🏋🏻')).toBe(true);
});
it('should not detect lifter', () => {
- expect(isSkinToneComboEmoji('🏋')).toBeFalsy();
+ expect(isSkinToneComboEmoji('🏋')).toBe(false);
});
it('should detect rowboat_tone4', () => {
- expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy();
+ expect(isSkinToneComboEmoji('🚣🏾')).toBe(true);
});
it('should not detect rowboat', () => {
- expect(isSkinToneComboEmoji('🚣')).toBeFalsy();
+ expect(isSkinToneComboEmoji('🚣')).toBe(false);
});
it('should not detect individual tone emoji', () => {
- expect(isSkinToneComboEmoji('🏻')).toBeFalsy();
+ expect(isSkinToneComboEmoji('🏻')).toBe(false);
});
});
describe('isHorceRacingSkinToneComboEmoji', () => {
it('should gracefully handle empty string', () => {
- expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy();
+ expect(isHorceRacingSkinToneComboEmoji('')).toBeUndefined();
});
it('should detect horse_racing_tone2', () => {
- expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy();
+ expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBe(true);
});
it('should not detect horse_racing', () => {
- expect(isHorceRacingSkinToneComboEmoji('🏇')).toBeFalsy();
+ expect(isHorceRacingSkinToneComboEmoji('🏇')).toBe(false);
});
});
describe('isPersonZwjEmoji', () => {
it('should gracefully handle empty string', () => {
- expect(isPersonZwjEmoji('')).toBeFalsy();
+ expect(isPersonZwjEmoji('')).toBe(false);
});
it('should detect couple_mm', () => {
- expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBeTruthy();
+ expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBe(true);
});
it('should not detect couple_with_heart', () => {
- expect(isPersonZwjEmoji('💑')).toBeFalsy();
+ expect(isPersonZwjEmoji('💑')).toBe(false);
});
it('should not detect couplekiss', () => {
- expect(isPersonZwjEmoji('💏')).toBeFalsy();
+ expect(isPersonZwjEmoji('💏')).toBe(false);
});
it('should detect family_mmb', () => {
- expect(isPersonZwjEmoji('👨‍👨‍👦')).toBeTruthy();
+ expect(isPersonZwjEmoji('👨‍👨‍👦')).toBe(true);
});
it('should detect family_mwgb', () => {
- expect(isPersonZwjEmoji('👨‍👩‍👧‍👦')).toBeTruthy();
+ expect(isPersonZwjEmoji('👨‍👩‍👧‍👦')).toBe(true);
});
it('should not detect family', () => {
- expect(isPersonZwjEmoji('👪')).toBeFalsy();
+ expect(isPersonZwjEmoji('👪')).toBe(false);
});
it('should detect kiss_ww', () => {
- expect(isPersonZwjEmoji('👩‍❤️‍💋‍👩')).toBeTruthy();
+ expect(isPersonZwjEmoji('👩‍❤️‍💋‍👩')).toBe(true);
});
it('should not detect girl', () => {
- expect(isPersonZwjEmoji('👧')).toBeFalsy();
+ expect(isPersonZwjEmoji('👧')).toBe(false);
});
it('should not detect girl_tone5', () => {
- expect(isPersonZwjEmoji('👧🏿')).toBeFalsy();
+ expect(isPersonZwjEmoji('👧🏿')).toBe(false);
});
it('should not detect man', () => {
- expect(isPersonZwjEmoji('👨')).toBeFalsy();
+ expect(isPersonZwjEmoji('👨')).toBe(false);
});
it('should not detect woman', () => {
- expect(isPersonZwjEmoji('👩')).toBeFalsy();
+ expect(isPersonZwjEmoji('👩')).toBe(false);
});
});
@@ -298,13 +298,13 @@ describe('emoji', () => {
it('should gracefully handle empty string with unicode support', () => {
const isSupported = isEmojiUnicodeSupported({ '1.0': true }, '', '1.0');
- expect(isSupported).toBeTruthy();
+ expect(isSupported).toBe(true);
});
it('should gracefully handle empty string without unicode support', () => {
const isSupported = isEmojiUnicodeSupported({}, '', '1.0');
- expect(isSupported).toBeFalsy();
+ expect(isSupported).toBeUndefined();
});
it('bomb(6.0) with 6.0 support', () => {
@@ -316,7 +316,7 @@ describe('emoji', () => {
emojiFixtureMap[emojiKey].unicodeVersion,
);
- expect(isSupported).toBeTruthy();
+ expect(isSupported).toBe(true);
});
it('bomb(6.0) without 6.0 support', () => {
@@ -328,7 +328,7 @@ describe('emoji', () => {
emojiFixtureMap[emojiKey].unicodeVersion,
);
- expect(isSupported).toBeFalsy();
+ expect(isSupported).toBe(false);
});
it('bomb(6.0) without 6.0 but with 9.0 support', () => {
@@ -340,7 +340,7 @@ describe('emoji', () => {
emojiFixtureMap[emojiKey].unicodeVersion,
);
- expect(isSupported).toBeFalsy();
+ expect(isSupported).toBe(false);
});
it('construction_worker_tone5(8.0) without skin tone modifier support', () => {
@@ -367,7 +367,7 @@ describe('emoji', () => {
emojiFixtureMap[emojiKey].unicodeVersion,
);
- expect(isSupported).toBeFalsy();
+ expect(isSupported).toBe(false);
});
it('use native keycap on >=57 chrome', () => {
@@ -386,7 +386,7 @@ describe('emoji', () => {
emojiFixtureMap[emojiKey].unicodeVersion,
);
- expect(isSupported).toBeTruthy();
+ expect(isSupported).toBe(true);
});
it('fallback keycap on <57 chrome', () => {
@@ -405,7 +405,7 @@ describe('emoji', () => {
emojiFixtureMap[emojiKey].unicodeVersion,
);
- expect(isSupported).toBeFalsy();
+ expect(isSupported).toBe(false);
});
});
diff --git a/spec/frontend/environments/deployment_spec.js b/spec/frontend/environments/deployment_spec.js
index 6cc363e000b..4cbbb60b74c 100644
--- a/spec/frontend/environments/deployment_spec.js
+++ b/spec/frontend/environments/deployment_spec.js
@@ -1,4 +1,6 @@
-import { GlCollapse } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { useFakeDate } from 'helpers/fake_date';
import { stubTransition } from 'helpers/stub_transition';
@@ -8,9 +10,13 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Deployment from '~/environments/components/deployment.vue';
import Commit from '~/environments/components/commit.vue';
import DeploymentStatusBadge from '~/environments/components/deployment_status_badge.vue';
-import { resolvedEnvironment } from './graphql/mock_data';
+import createMockApollo from '../__helpers__/mock_apollo_helper';
+import waitForPromises from '../__helpers__/wait_for_promises';
+import getDeploymentDetails from '../../../app/assets/javascripts/environments/graphql/queries/deployment_details.query.graphql';
+import { resolvedEnvironment, resolvedDeploymentDetails } from './graphql/mock_data';
describe('~/environments/components/deployment.vue', () => {
+ Vue.use(VueApollo);
useFakeDate(2022, 0, 8, 16);
let deployment;
@@ -20,14 +26,23 @@ describe('~/environments/components/deployment.vue', () => {
deployment = resolvedEnvironment.lastDeployment;
});
- const createWrapper = ({ propsData = {} } = {}) =>
- mountExtended(Deployment, {
+ const createWrapper = ({ propsData = {}, options = {} } = {}) => {
+ const mockApollo = createMockApollo([
+ [getDeploymentDetails, jest.fn().mockResolvedValue(resolvedDeploymentDetails)],
+ ]);
+
+ return mountExtended(Deployment, {
+ stubs: { transition: stubTransition() },
propsData: {
deployment,
+ visible: true,
...propsData,
},
- stubs: { transition: stubTransition() },
+ apolloProvider: mockApollo,
+ provide: { projectPath: '/1' },
+ ...options,
});
+ };
afterEach(() => {
wrapper?.destroy();
@@ -102,10 +117,11 @@ describe('~/environments/components/deployment.vue', () => {
});
it('shows the short SHA for the commit of the deployment', () => {
- const sha = wrapper.findByTitle(__('Commit SHA'));
+ const sha = wrapper.findByRole('link', { name: __('Commit SHA') });
expect(sha.exists()).toBe(true);
expect(sha.text()).toBe(deployment.commit.shortId);
+ expect(sha.attributes('href')).toBe(deployment.commit.commitPath);
});
it('shows the commit icon', () => {
@@ -183,29 +199,12 @@ describe('~/environments/components/deployment.vue', () => {
});
});
- describe('collapse', () => {
- let collapse;
- let button;
-
+ describe('details', () => {
beforeEach(() => {
wrapper = createWrapper();
- collapse = wrapper.findComponent(GlCollapse);
- button = wrapper.findComponent({ ref: 'details-toggle' });
});
- it('is collapsed by default', () => {
- expect(collapse.attributes('visible')).toBeUndefined();
- expect(button.props('icon')).toBe('expand-down');
- expect(button.text()).toBe(__('Show details'));
- });
-
- it('opens on click', async () => {
- await button.trigger('click');
-
- expect(button.text()).toBe(__('Hide details'));
- expect(button.props('icon')).toBe('expand-up');
- expect(collapse.attributes('visible')).toBe('visible');
-
+ it('shows information about the deployment', () => {
const username = wrapper.findByRole('link', { name: `@${deployment.user.username}` });
expect(username.attributes('href')).toBe(deployment.user.path);
@@ -221,24 +220,43 @@ describe('~/environments/components/deployment.vue', () => {
const ref = wrapper.findByRole('link', { name: deployment.ref.name });
expect(ref.attributes('href')).toBe(deployment.ref.refPath);
});
+
+ it('shows information about tags related to the deployment', async () => {
+ expect(wrapper.findByText(__('Tags')).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+
+ await waitForPromises();
+
+ for (let i = 1; i < 6; i += 1) {
+ const tagName = __(`testTag${i}`);
+ const testTag = wrapper.findByText(tagName);
+ expect(testTag.exists()).toBe(true);
+ expect(testTag.attributes('href')).toBe(`tags/${tagName}`);
+ }
+ expect(wrapper.findByText(__('testTag6')).exists()).toBe(false);
+ expect(wrapper.findByText(__('Tag')).exists()).toBe(false);
+ // with more than 5 tags, show overflow marker
+ expect(wrapper.findByText('...').exists()).toBe(true);
+ });
});
describe('with tagged deployment', () => {
- beforeEach(async () => {
+ beforeEach(() => {
wrapper = createWrapper({ propsData: { deployment: { ...deployment, tag: true } } });
- await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click');
});
- it('shows tag instead of branch', () => {
- const refLabel = wrapper.findByText(__('Tag'));
+ it('shows tags instead of branch', () => {
+ const refLabel = wrapper.findByText(__('Tags'));
expect(refLabel.exists()).toBe(true);
+
+ const branchLabel = wrapper.findByText(__('Branch'));
+ expect(branchLabel.exists()).toBe(false);
});
});
describe('with API deployment', () => {
- beforeEach(async () => {
+ beforeEach(() => {
wrapper = createWrapper({ propsData: { deployment: { ...deployment, deployable: null } } });
- await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click');
});
it('shows API instead of a job name', () => {
@@ -247,13 +265,12 @@ describe('~/environments/components/deployment.vue', () => {
});
});
describe('without a job path', () => {
- beforeEach(async () => {
+ beforeEach(() => {
wrapper = createWrapper({
propsData: {
deployment: { ...deployment, deployable: { name: deployment.deployable.name } },
},
});
- await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click');
});
it('shows a span instead of a link', () => {
diff --git a/spec/frontend/environments/environment_table_spec.js b/spec/frontend/environments/environment_table_spec.js
index 49a643aaac8..a86cfdd56ba 100644
--- a/spec/frontend/environments/environment_table_spec.js
+++ b/spec/frontend/environments/environment_table_spec.js
@@ -363,7 +363,7 @@ describe('Environment table', () => {
});
describe('sortedEnvironments', () => {
- it('it should sort children as well', () => {
+ it('should sort children as well', () => {
const mockItems = [
{
name: 'production',
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
index 57f98c81124..aff54107d6b 100644
--- a/spec/frontend/environments/environments_app_spec.js
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -50,6 +50,7 @@ describe('~/environments/components/environments_app.vue', () => {
defaultBranchName: 'main',
helpPagePath: '/help',
projectId: '1',
+ projectPath: '/1',
...provide,
},
apolloProvider,
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index 7e436476a8f..d246641b94b 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -757,3 +757,41 @@ export const resolvedFolder = {
stoppedCount: 0,
__typename: 'LocalEnvironmentFolder',
};
+
+export const resolvedDeploymentDetails = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/20',
+ deployment: {
+ id: 'gid://gitlab/Deployment/99',
+ iid: '55',
+ tags: [
+ {
+ name: 'testTag1',
+ path: 'tags/testTag1',
+ },
+ {
+ name: 'testTag2',
+ path: 'tags/testTag2',
+ },
+ {
+ name: 'testTag3',
+ path: 'tags/testTag3',
+ },
+ {
+ name: 'testTag4',
+ path: 'tags/testTag4',
+ },
+ {
+ name: 'testTag5',
+ path: 'tags/testTag5',
+ },
+ {
+ name: 'testTag6',
+ path: 'tags/testTag6',
+ },
+ ],
+ },
+ },
+ },
+};
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
index a151595bf64..76cd09cfb4e 100644
--- a/spec/frontend/environments/new_environment_item_spec.js
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -24,7 +24,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
mountExtended(EnvironmentItem, {
apolloProvider,
propsData: { environment: resolvedEnvironment, ...propsData },
- provide: { helpPagePath: '/help', projectId: '1' },
+ provide: { helpPagePath: '/help', projectId: '1', projectPath: '/1' },
stubs: { transition: stubTransition() },
});
diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js
index 5a1c1c7714c..2405cb82eac 100644
--- a/spec/frontend/environments/new_environment_spec.js
+++ b/spec/frontend/environments/new_environment_spec.js
@@ -65,7 +65,7 @@ describe('~/environments/components/new.vue', () => {
input | value
${() => name} | ${'test'}
${() => url} | ${'https://example.org'}
- `('it changes the value of the input to $value', async ({ input, value }) => {
+ `('changes the value of the input to $value', async ({ input, value }) => {
await input().setValue(value);
expect(input().element.value).toBe(value);
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 b7dffbbec04..805ada54509 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -164,19 +164,19 @@ describe('ErrorTrackingList', () => {
expect(findSortDropdown().exists()).toBe(true);
});
- it('it searches by query', () => {
+ it('searches by query', () => {
findSearchBox().vm.$emit('input', 'search');
findSearchBox().trigger('keyup.enter');
expect(actions.searchByQuery.mock.calls[0][1]).toBe('search');
});
- it('it sorts by fields', () => {
+ it('sorts by fields', () => {
const findSortItem = () => findSortDropdown().find('.dropdown-item');
findSortItem().trigger('click');
expect(actions.sortByField).toHaveBeenCalled();
});
- it('it filters by status', () => {
+ it('filters by status', () => {
const findStatusFilter = () => findStatusFilterDropdown().find('.dropdown-item');
findStatusFilter().trigger('click');
expect(actions.filterByStatus).toHaveBeenCalled();
diff --git a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
index 693fcff50ca..0de4277b08a 100644
--- a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
+++ b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
@@ -62,7 +62,7 @@ describe('Stacktrace Entry', () => {
);
});
- it('should render only lineNo:columnNO when there is no errorFn ', () => {
+ it('should render only lineNo:columnNO when there is no errorFn', () => {
const extraInfo = { errorLine: 34, errorFn: null, errorColumn: 77 };
mountComponent({ expanded: false, lines: [], ...extraInfo });
const fileHeaderContent = trimText(findFileHeaderContent());
@@ -70,7 +70,7 @@ describe('Stacktrace Entry', () => {
expect(fileHeaderContent).toContain(`${extraInfo.errorLine}:${extraInfo.errorColumn}`);
});
- it('should render only lineNo when there is no errorColumn ', () => {
+ it('should render only lineNo when there is no errorColumn', () => {
const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: null };
mountComponent({ expanded: false, lines: [], ...extraInfo });
const fileHeaderContent = trimText(findFileHeaderContent());
diff --git a/spec/frontend/error_tracking_settings/components/app_spec.js b/spec/frontend/error_tracking_settings/components/app_spec.js
index c660c9c4a99..7a714cc1ebc 100644
--- a/spec/frontend/error_tracking_settings/components/app_spec.js
+++ b/spec/frontend/error_tracking_settings/components/app_spec.js
@@ -76,23 +76,23 @@ describe('error tracking settings app', () => {
describe('section', () => {
it('renders the form and dropdown', () => {
- expect(wrapper.find(ErrorTrackingForm).exists()).toBeTruthy();
- expect(wrapper.find(ProjectDropdown).exists()).toBeTruthy();
+ expect(wrapper.findComponent(ErrorTrackingForm).exists()).toBe(true);
+ expect(wrapper.findComponent(ProjectDropdown).exists()).toBe(true);
});
it('renders the Save Changes button', () => {
- expect(wrapper.find('.js-error-tracking-button').exists()).toBeTruthy();
+ expect(wrapper.find('.js-error-tracking-button').exists()).toBe(true);
});
it('enables the button by default', () => {
- expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeFalsy();
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeUndefined();
});
it('disables the button when saving', async () => {
store.state.settingsLoading = true;
await nextTick();
- expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy();
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBe('true');
});
});
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 b44af547658..c9095441d41 100644
--- a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
+++ b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
@@ -42,7 +42,7 @@ describe('error tracking settings project dropdown', () => {
describe('empty project list', () => {
it('renders the dropdown', () => {
expect(wrapper.find('#project-dropdown').exists()).toBe(true);
- expect(wrapper.find(GlDropdown).exists()).toBe(true);
+ expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
});
it('shows helper text', () => {
@@ -57,8 +57,8 @@ describe('error tracking settings project dropdown', () => {
});
it('does not contain any dropdown items', () => {
- expect(wrapper.find(GlDropdownItem).exists()).toBe(false);
- expect(wrapper.find(GlDropdown).props('text')).toBe('No projects available');
+ expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(false);
+ expect(wrapper.findComponent(GlDropdown).props('text')).toBe('No projects available');
});
});
@@ -71,12 +71,12 @@ describe('error tracking settings project dropdown', () => {
it('renders the dropdown', () => {
expect(wrapper.find('#project-dropdown').exists()).toBe(true);
- expect(wrapper.find(GlDropdown).exists()).toBe(true);
+ expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
});
it('contains a number of dropdown items', () => {
- expect(wrapper.find(GlDropdownItem).exists()).toBe(true);
- expect(wrapper.findAll(GlDropdownItem).length).toBe(2);
+ expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(true);
+ expect(wrapper.findAllComponents(GlDropdownItem).length).toBe(2);
});
});
diff --git a/spec/frontend/feature_flags/components/environments_dropdown_spec.js b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
index e8103df78bc..2b9710c9085 100644
--- a/spec/frontend/feature_flags/components/environments_dropdown_spec.js
+++ b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
@@ -8,7 +8,7 @@ import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdo
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
-describe('Feature flags > Environments dropdown ', () => {
+describe('Feature flags > Environments dropdown', () => {
let wrapper;
let mock;
const results = ['production', 'staging'];
diff --git a/spec/frontend/feature_flags/store/edit/actions_spec.js b/spec/frontend/feature_flags/store/edit/actions_spec.js
index b6114cb0c9f..7132e83a940 100644
--- a/spec/frontend/feature_flags/store/edit/actions_spec.js
+++ b/spec/frontend/feature_flags/store/edit/actions_spec.js
@@ -40,7 +40,7 @@ describe('Feature flags Edit Module actions', () => {
});
describe('success', () => {
- it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess ', () => {
+ it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess', () => {
const featureFlag = {
name: 'name',
description: 'description',
@@ -75,7 +75,7 @@ describe('Feature flags Edit Module actions', () => {
});
describe('error', () => {
- it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagError ', () => {
+ it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagError', () => {
mock.onPut(`${TEST_HOST}/endpoint.json`).replyOnce(500, { message: [] });
return testAction(
@@ -154,7 +154,7 @@ describe('Feature flags Edit Module actions', () => {
});
describe('success', () => {
- it('dispatches requestFeatureFlag and receiveFeatureFlagSuccess ', () => {
+ it('dispatches requestFeatureFlag and receiveFeatureFlagSuccess', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 1 });
return testAction(
@@ -176,7 +176,7 @@ describe('Feature flags Edit Module actions', () => {
});
describe('error', () => {
- it('dispatches requestFeatureFlag and receiveUpdateFeatureFlagError ', () => {
+ it('dispatches requestFeatureFlag and receiveUpdateFeatureFlagError', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`, {}).replyOnce(500, {});
return testAction(
diff --git a/spec/frontend/feature_flags/store/index/actions_spec.js b/spec/frontend/feature_flags/store/index/actions_spec.js
index ce62c3b0473..96a7d868316 100644
--- a/spec/frontend/feature_flags/store/index/actions_spec.js
+++ b/spec/frontend/feature_flags/store/index/actions_spec.js
@@ -56,7 +56,7 @@ describe('Feature flags actions', () => {
});
describe('success', () => {
- it('dispatches requestFeatureFlags and receiveFeatureFlagsSuccess ', () => {
+ it('dispatches requestFeatureFlags and receiveFeatureFlagsSuccess', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, getRequestData, {});
return testAction(
@@ -78,7 +78,7 @@ describe('Feature flags actions', () => {
});
describe('error', () => {
- it('dispatches requestFeatureFlags and receiveFeatureFlagsError ', () => {
+ it('dispatches requestFeatureFlags and receiveFeatureFlagsError', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`, {}).replyOnce(500, {});
return testAction(
@@ -153,7 +153,7 @@ describe('Feature flags actions', () => {
});
describe('success', () => {
- it('dispatches requestRotateInstanceId and receiveRotateInstanceIdSuccess ', () => {
+ it('dispatches requestRotateInstanceId and receiveRotateInstanceIdSuccess', () => {
mock.onPost(`${TEST_HOST}/endpoint.json`).replyOnce(200, rotateData, {});
return testAction(
@@ -175,7 +175,7 @@ describe('Feature flags actions', () => {
});
describe('error', () => {
- it('dispatches requestRotateInstanceId and receiveRotateInstanceIdError ', () => {
+ it('dispatches requestRotateInstanceId and receiveRotateInstanceIdError', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`, {}).replyOnce(500, {});
return testAction(
diff --git a/spec/frontend/feature_flags/store/new/actions_spec.js b/spec/frontend/feature_flags/store/new/actions_spec.js
index 1dcd2da1d93..dbe6669c868 100644
--- a/spec/frontend/feature_flags/store/new/actions_spec.js
+++ b/spec/frontend/feature_flags/store/new/actions_spec.js
@@ -33,7 +33,7 @@ describe('Feature flags New Module Actions', () => {
});
describe('success', () => {
- it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagSuccess ', () => {
+ it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagSuccess', () => {
const actionParams = {
name: 'name',
description: 'description',
@@ -68,7 +68,7 @@ describe('Feature flags New Module Actions', () => {
});
describe('error', () => {
- it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagError ', () => {
+ it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagError', () => {
const actionParams = {
name: 'name',
description: 'description',
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 897ad5ee2bf..91457f10bf8 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,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
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';
@@ -6,12 +6,12 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
describe('Recent Searches Dropdown Content', () => {
let wrapper;
- const findLocalStorageNote = () => wrapper.find({ ref: 'localStorageNote' });
- const findDropdownItems = () => wrapper.findAll({ ref: 'dropdownItem' });
- const findDropdownNote = () => wrapper.find({ ref: 'dropdownNote' });
+ const findLocalStorageNote = () => wrapper.findByTestId('local-storage-note');
+ const findDropdownItems = () => wrapper.findAllByTestId('dropdown-item');
+ const findDropdownNote = () => wrapper.findByTestId('dropdown-note');
const createComponent = (props) => {
- wrapper = shallowMount(RecentSearchesDropdownContent, {
+ wrapper = shallowMountExtended(RecentSearchesDropdownContent, {
propsData: {
allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(),
items: [],
@@ -94,7 +94,7 @@ describe('Recent Searches Dropdown Content', () => {
});
it('emits requestClearRecentSearches on Clear resent searches button', () => {
- wrapper.find({ ref: 'clearButton' }).trigger('click');
+ wrapper.findByTestId('clear-button').trigger('click');
expect(onRequestClearRecentSearchesSpy).toHaveBeenCalled();
});
diff --git a/spec/frontend/filtered_search/droplab/drop_down_spec.js b/spec/frontend/filtered_search/droplab/drop_down_spec.js
index f49dbfcf79c..6fbb4394944 100644
--- a/spec/frontend/filtered_search/droplab/drop_down_spec.js
+++ b/spec/frontend/filtered_search/droplab/drop_down_spec.js
@@ -557,11 +557,11 @@ describe('DropLab DropDown', () => {
DropDown.prototype.show.call(testContext.dropdown);
});
- it('it should set .list display to block', () => {
+ it('should set .list display to block', () => {
expect(testContext.list.style.display).toBe('block');
});
- it('it should set .hidden to false', () => {
+ it('should set .hidden to false', () => {
expect(testContext.dropdown.hidden).toBe(false);
});
@@ -591,11 +591,11 @@ describe('DropLab DropDown', () => {
DropDown.prototype.hide.call(testContext.dropdown);
});
- it('it should set .list display to none', () => {
+ it('should set .list display to none', () => {
expect(testContext.list.style.display).toBe('none');
});
- it('it should set .hidden to true', () => {
+ it('should set .hidden to true', () => {
expect(testContext.dropdown.hidden).toBe(true);
});
});
@@ -648,11 +648,11 @@ describe('DropLab DropDown', () => {
DropDown.prototype.destroy.call(testContext.dropdown);
});
- it('it should call .hide', () => {
+ it('should call .hide', () => {
expect(testContext.dropdown.hide).toHaveBeenCalled();
});
- it('it should call .removeEventListener', () => {
+ it('should call .removeEventListener', () => {
expect(testContext.list.removeEventListener).toHaveBeenCalledWith(
'click',
testContext.eventWrapper.clickEvent,
diff --git a/spec/frontend/fixtures/api_merge_requests.rb b/spec/frontend/fixtures/api_merge_requests.rb
index 75bc8c8df25..7d95c506e6c 100644
--- a/spec/frontend/fixtures/api_merge_requests.rb
+++ b/spec/frontend/fixtures/api_merge_requests.rb
@@ -7,7 +7,7 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
include JavaScriptFixturesHelpers
let_it_be(:admin) { create(:admin, name: 'root') }
- let_it_be(:namespace) { create(:namespace, name: 'gitlab-test' )}
+ let_it_be(:namespace) { create(:namespace, name: 'gitlab-test' ) }
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'lorem-ipsum') }
let_it_be(:early_mrs) do
4.times { |i| create(:merge_request, source_project: project, source_branch: "branch-#{i}") }
diff --git a/spec/frontend/fixtures/api_projects.rb b/spec/frontend/fixtures/api_projects.rb
index eada2f8e0f7..5acc1095d5c 100644
--- a/spec/frontend/fixtures/api_projects.rb
+++ b/spec/frontend/fixtures/api_projects.rb
@@ -7,7 +7,7 @@ RSpec.describe API::Projects, '(JavaScript fixtures)', type: :request do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin, name: 'root') }
- let(:namespace) { create(:namespace, name: 'gitlab-test' )}
+ let(:namespace) { create(:namespace, name: 'gitlab-test' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'lorem-ipsum') }
let(:project_empty) { create(:project_empty_repo, namespace: namespace, path: 'lorem-ipsum-empty') }
diff --git a/spec/frontend/fixtures/application_settings.rb b/spec/frontend/fixtures/application_settings.rb
index a7a989f31ec..b3ce23c8cd7 100644
--- a/spec/frontend/fixtures/application_settings.rb
+++ b/spec/frontend/fixtures/application_settings.rb
@@ -8,7 +8,7 @@ RSpec.describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', ty
include AdminModeHelper
let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
before do
diff --git a/spec/frontend/fixtures/blob.rb b/spec/frontend/fixtures/blob.rb
index b2bbdd2749e..54c5b83da3e 100644
--- a/spec/frontend/fixtures/blob.rb
+++ b/spec/frontend/fixtures/blob.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
let(:user) { project.first_owner }
diff --git a/spec/frontend/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index b3bb4b8873a..6cda2f0f665 100644
--- a/spec/frontend/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Branches (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
let_it_be(:user) { project.first_owner }
diff --git a/spec/frontend/fixtures/clusters.rb b/spec/frontend/fixtures/clusters.rb
index 49596d98774..426a76f29e0 100644
--- a/spec/frontend/fixtures/clusters.rb
+++ b/spec/frontend/fixtures/clusters.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::ClustersController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace) }
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let(:user) { project.first_owner }
diff --git a/spec/frontend/fixtures/deploy_keys.rb b/spec/frontend/fixtures/deploy_keys.rb
index 154084e0181..24d602216d8 100644
--- a/spec/frontend/fixtures/deploy_keys.rb
+++ b/spec/frontend/fixtures/deploy_keys.rb
@@ -7,11 +7,11 @@ RSpec.describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :c
include AdminModeHelper
let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
- let(:project2) { create(:project, :internal)}
- let(:project3) { create(:project, :internal)}
- let(:project4) { create(:project, :internal)}
+ let(:project2) { create(:project, :internal) }
+ let(:project3) { create(:project, :internal) }
+ let(:project4) { create(:project, :internal) }
before do
# Using an admin for these fixtures because they are used for verifying a frontend
diff --git a/spec/frontend/fixtures/groups.rb b/spec/frontend/fixtures/groups.rb
index ddd436b98c6..9c22ff176ff 100644
--- a/spec/frontend/fixtures/groups.rb
+++ b/spec/frontend/fixtures/groups.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Groups (JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:user) { create(:user) }
- let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')}
+ let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre') }
before do
group.add_owner(user)
diff --git a/spec/frontend/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
index cde796497d4..e3d88098841 100644
--- a/spec/frontend/fixtures/issues.rb
+++ b/spec/frontend/fixtures/issues.rb
@@ -6,7 +6,7 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
include JavaScriptFixturesHelpers
let(:user) { create(:user, feed_token: 'feedtoken:coldfeed') }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
render_views
diff --git a/spec/frontend/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 2e15eefdce6..3657a5405a4 100644
--- a/spec/frontend/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Jobs (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
include GraphqlHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'builds-project') }
let(:user) { project.first_owner }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
diff --git a/spec/frontend/fixtures/labels.rb b/spec/frontend/fixtures/labels.rb
index 6736baed199..2445c9376e2 100644
--- a/spec/frontend/fixtures/labels.rb
+++ b/spec/frontend/fixtures/labels.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Labels (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let(:user) { create(:user) }
- let(:group) { create(:group, name: 'frontend-fixtures-group' )}
+ let(:group) { create(:group, name: 'frontend-fixtures-group' ) }
let(:project) { create(:project_empty_repo, namespace: group, path: 'labels-project') }
let!(:project_label_bug) { create(:label, project: project, title: 'bug', color: '#FF0000') }
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index cb4eb43b88d..cbf26a70e5f 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
let(:user) { project.first_owner }
diff --git a/spec/frontend/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
index 7f0d650b710..ff4b27844a6 100644
--- a/spec/frontend/fixtures/merge_requests_diffs.rb
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
let(:user) { project.first_owner }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, description: '- [ ] Task List Item') }
diff --git a/spec/frontend/fixtures/metrics_dashboard.rb b/spec/frontend/fixtures/metrics_dashboard.rb
index d59b01b04af..7f8b3d378d3 100644
--- a/spec/frontend/fixtures/metrics_dashboard.rb
+++ b/spec/frontend/fixtures/metrics_dashboard.rb
@@ -7,7 +7,7 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
- let_it_be(:namespace) { create(:namespace, name: 'monitoring' )}
+ let_it_be(:namespace) { create(:namespace, name: 'monitoring' ) }
let_it_be(:project) { project_with_dashboard_namespace('.gitlab/dashboards/test.yml', nil, namespace: namespace) }
let_it_be(:environment) { create(:environment, id: 1, project: project) }
let_it_be(:params) { { environment: environment } }
diff --git a/spec/frontend/fixtures/pipeline_schedules.rb b/spec/frontend/fixtures/pipeline_schedules.rb
index e155d27920d..5b7a445557e 100644
--- a/spec/frontend/fixtures/pipeline_schedules.rb
+++ b/spec/frontend/fixtures/pipeline_schedules.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :public, :repository) }
let(:user) { project.first_owner }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
diff --git a/spec/frontend/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
index 709e14183df..114db26d6a9 100644
--- a/spec/frontend/fixtures/pipelines.rb
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
let_it_be(:commit_without_author) { RepoHelpers.another_sample_commit }
diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
index fa7d61df3e8..b9c427c7505 100644
--- a/spec/frontend/fixtures/projects.rb
+++ b/spec/frontend/fixtures/projects.rb
@@ -8,7 +8,7 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
runners_token = 'runnerstoken:intabulasreferre'
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff', avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
diff --git a/spec/frontend/fixtures/raw.rb b/spec/frontend/fixtures/raw.rb
index b117cfea5fa..7bd5b8c5f6c 100644
--- a/spec/frontend/fixtures/raw.rb
+++ b/spec/frontend/fixtures/raw.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Raw files', '(JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'raw-project') }
let(:response) { @response }
diff --git a/spec/frontend/fixtures/search.rb b/spec/frontend/fixtures/search.rb
index db1ef67998f..b2da383d657 100644
--- a/spec/frontend/fixtures/search.rb
+++ b/spec/frontend/fixtures/search.rb
@@ -23,40 +23,41 @@ RSpec.describe SearchController, '(JavaScript fixtures)', type: :controller do
let(:namespace) { create(:namespace, name: 'frontend-fixtures') }
let(:project) { create(:project, :public, :repository, namespace: namespace, path: 'search-project') }
let(:blobs) do
- Kaminari.paginate_array([
- Gitlab::Search::FoundBlob.new(
- path: 'CHANGELOG',
- basename: 'CHANGELOG',
- ref: 'master',
- data: "hello\nworld\nfoo\nSend # this is the highligh\nbaz\nboo\nbat",
- project: project,
- project_id: project.id,
- startline: 2),
- Gitlab::Search::FoundBlob.new(
- path: 'CONTRIBUTING',
- basename: 'CONTRIBUTING',
- ref: 'master',
- data: "hello\nworld\nfoo\nSend # this is the highligh\nbaz\nboo\nbat",
- project: project,
- project_id: project.id,
- startline: 2),
- Gitlab::Search::FoundBlob.new(
- path: 'README',
- basename: 'README',
- ref: 'master',
- data: "foo\nSend # this is the highlight\nbaz\nboo\nbat",
- project: project,
- project_id: project.id,
- startline: 2),
- Gitlab::Search::FoundBlob.new(
- path: 'test',
- basename: 'test',
- ref: 'master',
- data: "foo\nSend # this is the highlight\nbaz\nboo\nbat",
- project: project,
- project_id: project.id,
- startline: 2)
- ],
+ Kaminari.paginate_array(
+ [
+ Gitlab::Search::FoundBlob.new(
+ path: 'CHANGELOG',
+ basename: 'CHANGELOG',
+ ref: 'master',
+ data: "hello\nworld\nfoo\nSend # this is the highligh\nbaz\nboo\nbat",
+ project: project,
+ project_id: project.id,
+ startline: 2),
+ Gitlab::Search::FoundBlob.new(
+ path: 'CONTRIBUTING',
+ basename: 'CONTRIBUTING',
+ ref: 'master',
+ data: "hello\nworld\nfoo\nSend # this is the highligh\nbaz\nboo\nbat",
+ project: project,
+ project_id: project.id,
+ startline: 2),
+ Gitlab::Search::FoundBlob.new(
+ path: 'README',
+ basename: 'README',
+ ref: 'master',
+ data: "foo\nSend # this is the highlight\nbaz\nboo\nbat",
+ project: project,
+ project_id: project.id,
+ startline: 2),
+ Gitlab::Search::FoundBlob.new(
+ path: 'test',
+ basename: 'test',
+ ref: 'master',
+ data: "foo\nSend # this is the highlight\nbaz\nboo\nbat",
+ project: project,
+ project_id: project.id,
+ startline: 2)
+ ],
total_count: 4,
limit: 4,
offset: 0)
diff --git a/spec/frontend/fixtures/snippet.rb b/spec/frontend/fixtures/snippet.rb
index f05ff3ee269..58d4bc5c1f3 100644
--- a/spec/frontend/fixtures/snippet.rb
+++ b/spec/frontend/fixtures/snippet.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe SnippetsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
let(:user) { project.first_owner }
let(:snippet) { create(:personal_snippet, :public, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: user) }
diff --git a/spec/frontend/fixtures/startup_css.rb b/spec/frontend/fixtures/startup_css.rb
index cf7383fa6ca..bd2d63a1827 100644
--- a/spec/frontend/fixtures/startup_css.rb
+++ b/spec/frontend/fixtures/startup_css.rb
@@ -69,11 +69,25 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
it_behaves_like 'startup css project fixtures', 'dark'
end
- describe RegistrationsController, '(Startup CSS fixtures)', type: :controller do
+ describe SessionsController, '(Startup CSS fixtures)', type: :controller do
+ include DeviseHelpers
+
+ before do
+ set_devise_mapping(context: request)
+ end
+
it 'startup_css/sign-in.html' do
get :new
expect(response).to be_successful
end
+
+ it 'startup_css/sign-in-old.html' do
+ stub_feature_flags(restyle_login_page: false)
+
+ get :new
+
+ expect(response).to be_successful
+ end
end
end
diff --git a/spec/frontend/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb
index 7dce09e8f49..d934396f803 100644
--- a/spec/frontend/fixtures/todos.rb
+++ b/spec/frontend/fixtures/todos.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Todos (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' ) }
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
let(:user) { project.first_owner }
let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js
index 6cd32ff6b40..e26c52f0bf7 100644
--- a/spec/frontend/flash_spec.js
+++ b/spec/frontend/flash_spec.js
@@ -36,7 +36,7 @@ describe('Flash', () => {
hideFlash(el, false);
expect(el.style.opacity).toBe('');
- expect(el.style.transition).toBeFalsy();
+ expect(el.style.transition).toHaveLength(0);
});
it('removes element after transitionend', () => {
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 eef5dc86c1a..e6673fa78ec 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,7 +1,7 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_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';
@@ -16,18 +16,18 @@ describe('FrequentItemsListItemComponent', () => {
let trackingSpy;
let store;
- const findTitle = () => wrapper.find({ ref: 'frequentItemsItemTitle' });
+ const findTitle = () => wrapper.findByTestId('frequent-items-item-title');
const findAvatar = () => wrapper.findComponent(ProjectAvatar);
- const findAllTitles = () => wrapper.findAll({ ref: 'frequentItemsItemTitle' });
- const findNamespace = () => wrapper.find({ ref: 'frequentItemsItemNamespace' });
+ const findAllTitles = () => wrapper.findAllByTestId('frequent-items-item-title');
+ const findNamespace = () => wrapper.findByTestId('frequent-items-item-namespace');
const findAllButtons = () => wrapper.findAllComponents(GlButton);
- const findAllNamespace = () => wrapper.findAll({ ref: 'frequentItemsItemNamespace' });
+ const findAllNamespace = () => wrapper.findAllByTestId('frequent-items-item-namespace');
const findAllAvatars = () => wrapper.findAllComponents(ProjectAvatar);
const findAllMetadataContainers = () =>
- wrapper.findAll({ ref: 'frequentItemsItemMetadataContainer' });
+ wrapper.findAllByTestId('frequent-items-item-metadata-container');
const createComponent = (props = {}) => {
- wrapper = shallowMount(frequentItemsListItemComponent, {
+ wrapper = shallowMountExtended(frequentItemsListItemComponent, {
store,
propsData: {
itemId: mockProject.id,
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 beaab1913d0..9f08a432a3d 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
@@ -1,6 +1,6 @@
-import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
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';
@@ -12,7 +12,7 @@ describe('FrequentItemsListComponent', () => {
let wrapper;
const createComponent = (props = {}) => {
- wrapper = mount(frequentItemsListComponent, {
+ wrapper = mountExtended(frequentItemsListComponent, {
store: createStore(),
propsData: {
namespace: 'projects',
@@ -94,8 +94,8 @@ describe('FrequentItemsListComponent', () => {
await nextTick();
expect(wrapper.classes('frequent-items-list-container')).toBe(true);
- expect(wrapper.findAll({ ref: 'frequentItemsList' })).toHaveLength(1);
- expect(wrapper.findAll(frequentItemsListItemComponent)).toHaveLength(5);
+ expect(wrapper.findAllByTestId('frequent-items-list')).toHaveLength(1);
+ expect(wrapper.findAllComponents(frequentItemsListItemComponent)).toHaveLength(5);
});
it('should render component element with empty message', async () => {
@@ -105,7 +105,7 @@ describe('FrequentItemsListComponent', () => {
await nextTick();
expect(wrapper.vm.$el.querySelectorAll('li.section-empty')).toHaveLength(1);
- expect(wrapper.findAll(frequentItemsListItemComponent)).toHaveLength(0);
+ expect(wrapper.findAllComponents(frequentItemsListItemComponent)).toHaveLength(0);
});
});
});
diff --git a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
index d0a4cf70f5f..94fc97b82c2 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
@@ -23,7 +23,7 @@ describe('FrequentItemsSearchInputComponent', () => {
},
});
- const findSearchBoxByType = () => wrapper.find(GlSearchBoxByType);
+ const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
beforeEach(() => {
store = createStore();
diff --git a/spec/frontend/frequent_items/utils_spec.js b/spec/frontend/frequent_items/utils_spec.js
index 33c655a6ffd..8d4c89bd48f 100644
--- a/spec/frontend/frequent_items/utils_spec.js
+++ b/spec/frontend/frequent_items/utils_spec.js
@@ -10,25 +10,25 @@ import { mockProject, unsortedFrequentItems, sortedFrequentItems } from './mock_
describe('Frequent Items utils spec', () => {
describe('isMobile', () => {
- it('returns true when the screen is medium ', () => {
+ it('returns true when the screen is medium', () => {
jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('md');
expect(isMobile()).toBe(true);
});
- it('returns true when the screen is small ', () => {
+ it('returns true when the screen is small', () => {
jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('sm');
expect(isMobile()).toBe(true);
});
- it('returns true when the screen is extra-small ', () => {
+ it('returns true when the screen is extra-small', () => {
jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('xs');
expect(isMobile()).toBe(true);
});
- it('returns false when the screen is larger than medium ', () => {
+ it('returns false when the screen is larger than medium', () => {
jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('lg');
expect(isMobile()).toBe(false);
diff --git a/spec/frontend/google_cloud/databases/panel_spec.js b/spec/frontend/google_cloud/databases/panel_spec.js
index 490c0136651..e6a0d74f348 100644
--- a/spec/frontend/google_cloud/databases/panel_spec.js
+++ b/spec/frontend/google_cloud/databases/panel_spec.js
@@ -2,6 +2,8 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import Panel from '~/google_cloud/databases/panel.vue';
import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
import GoogleCloudMenu from '~/google_cloud/components/google_cloud_menu.vue';
+import ServiceTable from '~/google_cloud/databases/service_table.vue';
+import InstanceTable from '~/google_cloud/databases/cloudsql/instance_table.vue';
describe('google_cloud/databases/panel', () => {
let wrapper;
@@ -10,6 +12,11 @@ describe('google_cloud/databases/panel', () => {
configurationUrl: 'configuration-url',
deploymentsUrl: 'deployments-url',
databasesUrl: 'databases-url',
+ cloudsqlPostgresUrl: 'cloudsql-postgres-url',
+ cloudsqlMysqlUrl: 'cloudsql-mysql-url',
+ cloudsqlSqlserverUrl: 'cloudsql-sqlserver-url',
+ cloudsqlInstances: [],
+ emptyIllustrationUrl: 'empty-illustration-url',
};
beforeEach(() => {
@@ -33,4 +40,14 @@ describe('google_cloud/databases/panel', () => {
expect(target.props('deploymentsUrl')).toBe(props.deploymentsUrl);
expect(target.props('databasesUrl')).toBe(props.databasesUrl);
});
+
+ it('contains Databases service table', () => {
+ const target = wrapper.findComponent(ServiceTable);
+ expect(target.exists()).toBe(true);
+ });
+
+ it('contains CloudSQL instance table', () => {
+ const target = wrapper.findComponent(InstanceTable);
+ expect(target.exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/google_tag_manager/index_spec.js b/spec/frontend/google_tag_manager/index_spec.js
index 6a7eb1fd9f1..ec9e1ef8e5f 100644
--- a/spec/frontend/google_tag_manager/index_spec.js
+++ b/spec/frontend/google_tag_manager/index_spec.js
@@ -8,13 +8,13 @@ import {
trackSaasTrialSubmit,
trackSaasTrialSkip,
trackSaasTrialGroup,
- trackSaasTrialProject,
trackSaasTrialGetStarted,
trackTrialAcceptTerms,
trackCheckout,
trackTransaction,
trackAddToCartUsageTab,
getNamespaceId,
+ trackCompanyForm,
} from '~/google_tag_manager';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { logError } from '~/lib/logger';
@@ -149,9 +149,6 @@ describe('~/google_tag_manager/index', () => {
createTestCase(trackSaasTrialGroup, {
forms: [{ cls: 'js-saas-trial-group', expectation: { event: 'saasTrialGroup' } }],
}),
- createTestCase(trackSaasTrialProject, {
- forms: [{ id: 'new_project', expectation: { event: 'saasTrialProject' } }],
- }),
createTestCase(trackProjectImport, {
links: [
{
@@ -440,6 +437,34 @@ describe('~/google_tag_manager/index', () => {
});
});
});
+
+ describe('when trackCompanyForm is invoked', () => {
+ it('with an ultimate trial', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackCompanyForm('ultimate_trial');
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(spy).toHaveBeenCalledWith({
+ event: 'aboutYourCompanyFormSubmit',
+ aboutYourCompanyType: 'ultimate_trial',
+ });
+ expect(logError).not.toHaveBeenCalled();
+ });
+
+ it('with a free account', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackCompanyForm('free_account');
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(spy).toHaveBeenCalledWith({
+ event: 'aboutYourCompanyFormSubmit',
+ aboutYourCompanyType: 'free_account',
+ });
+ expect(logError).not.toHaveBeenCalled();
+ });
+ });
});
describe.each([
@@ -452,11 +477,11 @@ describe('~/google_tag_manager/index', () => {
});
it('no ops', () => {
- setHTMLFixture(createHTML({ forms: [{ id: 'new_project' }] }));
+ setHTMLFixture(createHTML({ forms: [{ cls: 'js-saas-trial-group' }] }));
- trackSaasTrialProject();
+ trackSaasTrialGroup();
- triggerEvent('#new_project', 'submit');
+ triggerEvent('.js-saas-trial-group', 'submit');
expect(spy).not.toHaveBeenCalled();
expect(logError).not.toHaveBeenCalled();
@@ -477,11 +502,11 @@ describe('~/google_tag_manager/index', () => {
});
it('logs error', () => {
- setHTMLFixture(createHTML({ forms: [{ id: 'new_project' }] }));
+ setHTMLFixture(createHTML({ forms: [{ cls: 'js-saas-trial-group' }] }));
- trackSaasTrialProject();
+ trackSaasTrialGroup();
- triggerEvent('#new_project', 'submit');
+ triggerEvent('.js-saas-trial-group', 'submit');
expect(logError).toHaveBeenCalledWith(
'Unexpected error while pushing to dataLayer',
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index a6bbea648d2..a4a7530184d 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -40,7 +40,7 @@ describe('AppComponent', () => {
const store = new GroupsStore({ hideProjects: false });
const service = new GroupsService(mockEndpoint);
- const createShallowComponent = ({ propsData = {}, provide = {} } = {}) => {
+ const createShallowComponent = ({ propsData = {} } = {}) => {
store.state.pageInfo = mockPageInfo;
wrapper = shallowMount(appComponent, {
propsData: {
@@ -53,10 +53,6 @@ describe('AppComponent', () => {
mocks: {
$toast,
},
- provide: {
- renderEmptyState: false,
- ...provide,
- },
});
vm = wrapper.vm;
};
@@ -402,8 +398,7 @@ describe('AppComponent', () => {
({ action, groups, fromSearch, renderEmptyState, expected }) => {
it(expected ? 'renders empty state' : 'does not render empty state', async () => {
createShallowComponent({
- propsData: { action },
- provide: { renderEmptyState },
+ propsData: { action, renderEmptyState },
});
vm.updateGroups(groups, fromSearch);
@@ -420,7 +415,6 @@ describe('AppComponent', () => {
it('renders legacy empty state', async () => {
createShallowComponent({
propsData: { action: 'subgroups_and_projects' },
- provide: { renderEmptyState: false },
});
vm.updateGroups([], false);
@@ -481,7 +475,7 @@ describe('AppComponent', () => {
it('should render loading icon', async () => {
vm.isLoading = true;
await nextTick();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('should render groups tree', async () => {
@@ -494,7 +488,7 @@ describe('AppComponent', () => {
it('renders modal confirmation dialog', () => {
createShallowComponent();
- const findGlModal = wrapper.find(GlModal);
+ const findGlModal = wrapper.findComponent(GlModal);
expect(findGlModal.exists()).toBe(true);
expect(findGlModal.attributes('title')).toBe('Are you sure?');
diff --git a/spec/frontend/groups/components/empty_state_spec.js b/spec/frontend/groups/components/empty_state_spec.js
index c0e71e814d0..fbeaa32b1ec 100644
--- a/spec/frontend/groups/components/empty_state_spec.js
+++ b/spec/frontend/groups/components/empty_state_spec.js
@@ -68,7 +68,7 @@ describe('EmptyState', () => {
it('renders empty state', () => {
createComponent({ provide: { canCreateSubgroups: false, canCreateProjects: false } });
- expect(wrapper.find(GlEmptyState).props()).toMatchObject({
+ expect(wrapper.findComponent(GlEmptyState).props()).toMatchObject({
title: EmptyState.i18n.withoutLinks.title,
description: EmptyState.i18n.withoutLinks.description,
svgPath: defaultProvide.emptySubgroupIllustration,
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 9906f62878f..3aa66644c19 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -8,9 +8,9 @@ import { getGroupItemMicrodata } from '~/groups/store/utils';
import * as urlUtilities from '~/lib/utils/url_utility';
import { ITEM_TYPE } from '~/groups/constants';
import {
- VISIBILITY_LEVEL_PRIVATE,
- VISIBILITY_LEVEL_INTERNAL,
- VISIBILITY_LEVEL_PUBLIC,
+ VISIBILITY_LEVEL_PRIVATE_STRING,
+ VISIBILITY_LEVEL_INTERNAL_STRING,
+ VISIBILITY_LEVEL_PUBLIC_STRING,
} from '~/visibility_level/constants';
import { helpPagePath } from '~/helpers/help_page_helper';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -19,7 +19,7 @@ import { mockParentGroupItem, mockChildren } from '../mock_data';
const createComponent = (
propsData = { group: mockParentGroupItem, parentGroup: mockChildren[0] },
provide = {
- currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE,
+ currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE_STRING,
},
) => {
return mountExtended(GroupItem, {
@@ -274,7 +274,7 @@ describe('GroupItemComponent', () => {
${'itemscope'} | ${'itemscope'}
${'itemtype'} | ${'https://schema.org/Organization'}
${'itemprop'} | ${'subOrganization'}
- `('it does set correct $attr', ({ attr, value } = {}) => {
+ `('does set correct $attr', ({ attr, value } = {}) => {
expect(wrapper.attributes(attr)).toBe(value);
});
@@ -283,7 +283,7 @@ describe('GroupItemComponent', () => {
${'img'} | ${'logo'}
${'[data-testid="group-name"]'} | ${'name'}
${'[data-testid="group-description"]'} | ${'description'}
- `('it does set correct $selector', ({ selector, propValue } = {}) => {
+ `('does set correct $selector', ({ selector, propValue } = {}) => {
expect(wrapper.find(selector).attributes('itemprop')).toBe(propValue);
});
});
@@ -320,16 +320,16 @@ describe('GroupItemComponent', () => {
describe('when showing projects', () => {
describe.each`
- itemVisibility | currentGroupVisibility | isPopoverShown
- ${VISIBILITY_LEVEL_PRIVATE} | ${VISIBILITY_LEVEL_PUBLIC} | ${false}
- ${VISIBILITY_LEVEL_INTERNAL} | ${VISIBILITY_LEVEL_PUBLIC} | ${false}
- ${VISIBILITY_LEVEL_PUBLIC} | ${VISIBILITY_LEVEL_PUBLIC} | ${false}
- ${VISIBILITY_LEVEL_PRIVATE} | ${VISIBILITY_LEVEL_PRIVATE} | ${false}
- ${VISIBILITY_LEVEL_INTERNAL} | ${VISIBILITY_LEVEL_PRIVATE} | ${true}
- ${VISIBILITY_LEVEL_PUBLIC} | ${VISIBILITY_LEVEL_PRIVATE} | ${true}
- ${VISIBILITY_LEVEL_PRIVATE} | ${VISIBILITY_LEVEL_INTERNAL} | ${false}
- ${VISIBILITY_LEVEL_INTERNAL} | ${VISIBILITY_LEVEL_INTERNAL} | ${false}
- ${VISIBILITY_LEVEL_PUBLIC} | ${VISIBILITY_LEVEL_INTERNAL} | ${true}
+ itemVisibility | currentGroupVisibility | isPopoverShown
+ ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${false}
+ ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${false}
+ ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${false}
+ ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${false}
+ ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${true}
+ ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${true}
+ ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${false}
+ ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${false}
+ ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${true}
`(
'when item visibility is $itemVisibility and parent group visibility is $currentGroupVisibility',
({ itemVisibility, currentGroupVisibility, isPopoverShown }) => {
@@ -374,7 +374,7 @@ describe('GroupItemComponent', () => {
wrapper = createComponent({
group: {
...mockParentGroupItem,
- visibility: VISIBILITY_LEVEL_PUBLIC,
+ visibility: VISIBILITY_LEVEL_PUBLIC_STRING,
type: ITEM_TYPE.PROJECT,
},
parentGroup: mockChildren[0],
diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js
index 6c1eb373b7e..866868eff36 100644
--- a/spec/frontend/groups/components/groups_spec.js
+++ b/spec/frontend/groups/components/groups_spec.js
@@ -6,7 +6,7 @@ import GroupItemComponent from '~/groups/components/group_item.vue';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import GroupsComponent from '~/groups/components/groups.vue';
import eventHub from '~/groups/event_hub';
-import { VISIBILITY_LEVEL_PRIVATE } from '~/visibility_level/constants';
+import { VISIBILITY_LEVEL_PRIVATE_STRING } from '~/visibility_level/constants';
import { mockGroups, mockPageInfo } from '../mock_data';
describe('GroupsComponent', () => {
@@ -26,7 +26,7 @@ describe('GroupsComponent', () => {
...propsData,
},
provide: {
- currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE,
+ currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE_STRING,
},
});
};
diff --git a/spec/frontend/groups/components/invite_members_banner_spec.js b/spec/frontend/groups/components/invite_members_banner_spec.js
index 1924f400861..d25b45bd662 100644
--- a/spec/frontend/groups/components/invite_members_banner_spec.js
+++ b/spec/frontend/groups/components/invite_members_banner_spec.js
@@ -71,7 +71,7 @@ describe('InviteMembersBanner', () => {
describe('when the button is clicked', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- wrapper.find(GlBanner).vm.$emit('primary');
+ wrapper.findComponent(GlBanner).vm.$emit('primary');
});
it('calls openModal through the eventHub', () => {
@@ -92,7 +92,7 @@ describe('InviteMembersBanner', () => {
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
const dismissEvent = 'invite_members_banner_dismissed';
- wrapper.find(GlBanner).vm.$emit('close');
+ wrapper.findComponent(GlBanner).vm.$emit('close');
expect(trackingSpy).toHaveBeenCalledWith(trackCategory, dismissEvent, {
label: provide.trackLabel,
@@ -102,7 +102,7 @@ describe('InviteMembersBanner', () => {
describe('rendering', () => {
const findBanner = () => {
- return wrapper.find(GlBanner);
+ return wrapper.findComponent(GlBanner);
};
beforeEach(() => {
@@ -132,16 +132,16 @@ describe('InviteMembersBanner', () => {
});
it('should render the banner when not dismissed', () => {
- expect(wrapper.find(GlBanner).exists()).toBe(true);
+ expect(wrapper.findComponent(GlBanner).exists()).toBe(true);
});
it('should close the banner when dismiss is clicked', async () => {
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
- expect(wrapper.find(GlBanner).exists()).toBe(true);
- wrapper.find(GlBanner).vm.$emit('close');
+ expect(wrapper.findComponent(GlBanner).exists()).toBe(true);
+ wrapper.findComponent(GlBanner).vm.$emit('close');
await nextTick();
- expect(wrapper.find(GlBanner).exists()).toBe(false);
+ expect(wrapper.findComponent(GlBanner).exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/groups/components/item_caret_spec.js b/spec/frontend/groups/components/item_caret_spec.js
index 4bf92bb5642..2333f04bb2e 100644
--- a/spec/frontend/groups/components/item_caret_spec.js
+++ b/spec/frontend/groups/components/item_caret_spec.js
@@ -22,8 +22,8 @@ describe('ItemCaret', () => {
}
});
- const findAllGlIcons = () => wrapper.findAll(GlIcon);
- const findGlIcon = () => wrapper.find(GlIcon);
+ const findAllGlIcons = () => wrapper.findAllComponents(GlIcon);
+ const findGlIcon = () => wrapper.findComponent(GlIcon);
describe('template', () => {
it('renders component template correctly', () => {
diff --git a/spec/frontend/groups/components/item_stats_spec.js b/spec/frontend/groups/components/item_stats_spec.js
index fdc267bc14a..0c2912adc66 100644
--- a/spec/frontend/groups/components/item_stats_spec.js
+++ b/spec/frontend/groups/components/item_stats_spec.js
@@ -24,7 +24,7 @@ describe('ItemStats', () => {
}
});
- const findItemStatsValue = () => wrapper.find(ItemStatsValue);
+ const findItemStatsValue = () => wrapper.findComponent(ItemStatsValue);
describe('template', () => {
it('renders component container element correctly', () => {
diff --git a/spec/frontend/groups/components/item_stats_value_spec.js b/spec/frontend/groups/components/item_stats_value_spec.js
index 98186120a81..b9db83c7dd7 100644
--- a/spec/frontend/groups/components/item_stats_value_spec.js
+++ b/spec/frontend/groups/components/item_stats_value_spec.js
@@ -25,7 +25,7 @@ describe('ItemStatsValue', () => {
}
});
- const findGlIcon = () => wrapper.find(GlIcon);
+ const findGlIcon = () => wrapper.findComponent(GlIcon);
const findStatValue = () => wrapper.find('[data-testid="itemStatValue"]');
describe('template', () => {
diff --git a/spec/frontend/groups/components/item_type_icon_spec.js b/spec/frontend/groups/components/item_type_icon_spec.js
index f3652f1a410..aa00e82150b 100644
--- a/spec/frontend/groups/components/item_type_icon_spec.js
+++ b/spec/frontend/groups/components/item_type_icon_spec.js
@@ -23,7 +23,7 @@ describe('ItemTypeIcon', () => {
}
});
- const findGlIcon = () => wrapper.find(GlIcon);
+ const findGlIcon = () => wrapper.findComponent(GlIcon);
describe('template', () => {
it('renders component template correctly', () => {
diff --git a/spec/frontend/groups/components/overview_tabs_spec.js b/spec/frontend/groups/components/overview_tabs_spec.js
new file mode 100644
index 00000000000..352bf25b84f
--- /dev/null
+++ b/spec/frontend/groups/components/overview_tabs_spec.js
@@ -0,0 +1,187 @@
+import { GlTab } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import OverviewTabs from '~/groups/components/overview_tabs.vue';
+import GroupsApp from '~/groups/components/app.vue';
+import GroupsStore from '~/groups/store/groups_store';
+import GroupsService from '~/groups/service/groups_service';
+import { createRouter } from '~/groups/init_overview_tabs';
+import {
+ ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ ACTIVE_TAB_SHARED,
+ ACTIVE_TAB_ARCHIVED,
+} from '~/groups/constants';
+import axios from '~/lib/utils/axios_utils';
+
+const router = createRouter();
+
+describe('OverviewTabs', () => {
+ let wrapper;
+
+ const endpoints = {
+ subgroups_and_projects: '/groups/foobar/-/children.json',
+ shared: '/groups/foobar/-/shared_projects.json',
+ archived: '/groups/foobar/-/children.json?archived=only',
+ };
+
+ const routerMock = {
+ push: jest.fn(),
+ };
+
+ const createComponent = async ({
+ route = { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: 'foo/bar/baz' } },
+ } = {}) => {
+ wrapper = mountExtended(OverviewTabs, {
+ router,
+ provide: {
+ endpoints,
+ },
+ mocks: { $route: route, $router: routerMock },
+ });
+
+ await nextTick();
+ };
+
+ const findTabPanels = () => wrapper.findAllComponents(GlTab);
+ const findTab = (name) => wrapper.findByRole('tab', { name });
+ const findSelectedTab = () => wrapper.findByRole('tab', { selected: true });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(async () => {
+ // eslint-disable-next-line no-new
+ new AxiosMockAdapter(axios);
+ });
+
+ it('renders `Subgroups and projects` tab with `GroupsApp` component', async () => {
+ await createComponent();
+
+ const tabPanel = findTabPanels().at(0);
+
+ expect(tabPanel.vm.$attrs).toMatchObject({
+ title: OverviewTabs.i18n[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS],
+ lazy: false,
+ });
+ expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({
+ action: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ store: new GroupsStore({ showSchemaMarkup: true }),
+ service: new GroupsService(endpoints[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]),
+ hideProjects: false,
+ renderEmptyState: true,
+ });
+ });
+
+ it('renders `Shared projects` tab and renders `GroupsApp` component after clicking tab', async () => {
+ await createComponent();
+
+ const tabPanel = findTabPanels().at(1);
+
+ expect(tabPanel.vm.$attrs).toMatchObject({
+ title: OverviewTabs.i18n[ACTIVE_TAB_SHARED],
+ lazy: true,
+ });
+
+ await findTab(OverviewTabs.i18n[ACTIVE_TAB_SHARED]).trigger('click');
+
+ expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({
+ action: ACTIVE_TAB_SHARED,
+ store: new GroupsStore(),
+ service: new GroupsService(endpoints[ACTIVE_TAB_SHARED]),
+ hideProjects: false,
+ renderEmptyState: false,
+ });
+
+ expect(tabPanel.vm.$attrs.lazy).toBe(false);
+ });
+
+ it('renders `Archived projects` tab and renders `GroupsApp` component after clicking tab', async () => {
+ await createComponent();
+
+ const tabPanel = findTabPanels().at(2);
+
+ expect(tabPanel.vm.$attrs).toMatchObject({
+ title: OverviewTabs.i18n[ACTIVE_TAB_ARCHIVED],
+ lazy: true,
+ });
+
+ await findTab(OverviewTabs.i18n[ACTIVE_TAB_ARCHIVED]).trigger('click');
+
+ expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({
+ action: ACTIVE_TAB_ARCHIVED,
+ store: new GroupsStore(),
+ service: new GroupsService(endpoints[ACTIVE_TAB_ARCHIVED]),
+ hideProjects: false,
+ renderEmptyState: false,
+ });
+
+ expect(tabPanel.vm.$attrs.lazy).toBe(false);
+ });
+
+ describe.each([
+ [
+ { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: 'foo/bar/baz' } },
+ OverviewTabs.i18n[ACTIVE_TAB_SHARED],
+ {
+ name: ACTIVE_TAB_SHARED,
+ params: { group: ['foo', 'bar', 'baz'] },
+ },
+ ],
+ [
+ { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: ['foo', 'bar', 'baz'] } },
+ OverviewTabs.i18n[ACTIVE_TAB_SHARED],
+ {
+ name: ACTIVE_TAB_SHARED,
+ params: { group: ['foo', 'bar', 'baz'] },
+ },
+ ],
+ [
+ { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: 'foo' } },
+ OverviewTabs.i18n[ACTIVE_TAB_SHARED],
+ {
+ name: ACTIVE_TAB_SHARED,
+ params: { group: ['foo'] },
+ },
+ ],
+ [
+ { name: ACTIVE_TAB_SHARED, params: { group: 'foo/bar' } },
+ OverviewTabs.i18n[ACTIVE_TAB_ARCHIVED],
+ {
+ name: ACTIVE_TAB_ARCHIVED,
+ params: { group: ['foo', 'bar'] },
+ },
+ ],
+ [
+ { name: ACTIVE_TAB_SHARED, params: { group: 'foo/bar' } },
+ OverviewTabs.i18n[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS],
+ {
+ name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ params: { group: ['foo', 'bar'] },
+ },
+ ],
+ [
+ { name: ACTIVE_TAB_ARCHIVED, params: { group: ['foo'] } },
+ OverviewTabs.i18n[ACTIVE_TAB_SHARED],
+ {
+ name: ACTIVE_TAB_SHARED,
+ params: { group: ['foo'] },
+ },
+ ],
+ ])('when current route is %j', (currentRoute, tabToClick, expectedRoute) => {
+ beforeEach(async () => {
+ await createComponent({ route: currentRoute });
+ });
+
+ it(`sets ${OverviewTabs.i18n[currentRoute.name]} as active tab`, () => {
+ expect(findSelectedTab().text()).toBe(OverviewTabs.i18n[currentRoute.name]);
+ });
+
+ it(`pushes expected route when ${tabToClick} tab is clicked`, async () => {
+ await findTab(tabToClick).trigger('click');
+
+ expect(routerMock.push).toHaveBeenCalledWith(expectedRoute);
+ });
+ });
+});
diff --git a/spec/frontend/groups/components/visibility_level_dropdown_spec.js b/spec/frontend/groups/components/visibility_level_dropdown_spec.js
deleted file mode 100644
index 61b7bbb0833..00000000000
--- a/spec/frontend/groups/components/visibility_level_dropdown_spec.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Component from '~/groups/components/visibility_level_dropdown.vue';
-
-describe('Visibility Level Dropdown', () => {
- let wrapper;
-
- const options = [
- { level: 0, label: 'Private', description: 'Private description' },
- { level: 20, label: 'Public', description: 'Public description' },
- ];
- const defaultLevel = 0;
-
- const createComponent = (propsData) => {
- wrapper = shallowMount(Component, {
- propsData,
- });
- };
-
- beforeEach(() => {
- createComponent({
- visibilityLevelOptions: options,
- defaultLevel,
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- const hiddenInputValue = () =>
- wrapper.find("input[name='group[visibility_level]']").attributes('value');
- const dropdownText = () => wrapper.find(GlDropdown).props('text');
- const findDropdownItems = () =>
- wrapper.findAll(GlDropdownItem).wrappers.map((option) => ({
- text: option.text(),
- secondaryText: option.props('secondaryText'),
- }));
-
- describe('Default values', () => {
- it('sets the value of the hidden input to the default value', () => {
- expect(hiddenInputValue()).toBe(options[0].level.toString());
- });
-
- it('sets the text of the dropdown to the default value', () => {
- expect(dropdownText()).toBe(options[0].label);
- });
-
- it('shows all dropdown options', () => {
- expect(findDropdownItems()).toEqual(
- options.map(({ label, description }) => ({ text: label, secondaryText: description })),
- );
- });
- });
-
- describe('Selecting an option', () => {
- beforeEach(() => {
- wrapper.findAll(GlDropdownItem).at(1).vm.$emit('click');
- });
-
- it('sets the value of the hidden input to the selected value', () => {
- expect(hiddenInputValue()).toBe(options[1].level.toString());
- });
-
- it('sets the text of the dropdown to the selected value', () => {
- expect(dropdownText()).toBe(options[1].label);
- });
- });
-});
diff --git a/spec/frontend/header_search/init_spec.js b/spec/frontend/header_search/init_spec.js
index 9515ca8c812..40c1843d461 100644
--- a/spec/frontend/header_search/init_spec.js
+++ b/spec/frontend/header_search/init_spec.js
@@ -24,10 +24,10 @@ describe('Header Search EventListener', () => {
const addEventListenerSpy = jest.spyOn(searchInputBox, 'addEventListener');
initHeaderSearch();
- expect(addEventListenerSpy).toBeCalledTimes(2);
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
});
- it('removes event listener ', async () => {
+ it('removes event listener', async () => {
const searchInputBox = document?.querySelector('#search');
const removeEventListenerSpy = jest.spyOn(searchInputBox, 'removeEventListener');
jest.mock('~/header_search', () => ({ initHeaderSearchApp: jest.fn() }));
@@ -39,7 +39,7 @@ describe('Header Search EventListener', () => {
[cleanEventListeners],
);
- expect(removeEventListenerSpy).toBeCalledTimes(2);
+ expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
});
it('attaches new vue dropdown when feature flag is enabled', async () => {
@@ -53,7 +53,7 @@ describe('Header Search EventListener', () => {
() => {},
);
- expect(mockVueApp).toBeCalled();
+ expect(mockVueApp).toHaveBeenCalled();
});
it('attaches old vue dropdown when feature flag is disabled', async () => {
@@ -69,6 +69,6 @@ describe('Header Search EventListener', () => {
() => {},
);
- expect(mockLegacyApp).toBeCalled();
+ expect(mockLegacyApp).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/header_search/mock_data.js b/spec/frontend/header_search/mock_data.js
index 8ccd7fb17e3..3a8624ad9dd 100644
--- a/spec/frontend/header_search/mock_data.js
+++ b/spec/frontend/header_search/mock_data.js
@@ -223,6 +223,20 @@ export const MOCK_AUTOCOMPLETE_OPTIONS = [
export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
{
+ category: 'Groups',
+ data: [
+ {
+ category: 'Groups',
+ html_id: 'autocomplete-Groups-1',
+
+ id: 1,
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
+ url: 'group/1',
+ },
+ ],
+ },
+ {
category: 'Projects',
data: [
{
@@ -246,20 +260,6 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
],
},
{
- category: 'Groups',
- data: [
- {
- category: 'Groups',
- html_id: 'autocomplete-Groups-1',
-
- id: 1,
- label: 'Gitlab Org / MockGroup1',
- value: 'MockGroup1',
- url: 'group/1',
- },
- ],
- },
- {
category: 'Help',
data: [
{
@@ -275,6 +275,14 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
{
+ category: 'Groups',
+ html_id: 'autocomplete-Groups-1',
+ id: 1,
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
+ url: 'group/1',
+ },
+ {
category: 'Projects',
html_id: 'autocomplete-Projects-0',
id: 1,
@@ -291,14 +299,6 @@ export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
url: 'project/2',
},
{
- category: 'Groups',
- html_id: 'autocomplete-Groups-1',
- id: 1,
- label: 'Gitlab Org / MockGroup1',
- value: 'MockGroup1',
- url: 'group/1',
- },
- {
category: 'Help',
html_id: 'autocomplete-Help-3',
label: 'GitLab Help',
diff --git a/spec/frontend/header_search/store/actions_spec.js b/spec/frontend/header_search/store/actions_spec.js
index 1748d89a6d3..1ae149128ca 100644
--- a/spec/frontend/header_search/store/actions_spec.js
+++ b/spec/frontend/header_search/store/actions_spec.js
@@ -2,9 +2,18 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/header_search/store/actions';
import * as types from '~/header_search/store/mutation_types';
-import createState from '~/header_search/store/state';
+import initState from '~/header_search/store/state';
import axios from '~/lib/utils/axios_utils';
-import { MOCK_SEARCH, MOCK_AUTOCOMPLETE_OPTIONS_RES } from '../mock_data';
+import {
+ MOCK_SEARCH,
+ MOCK_AUTOCOMPLETE_OPTIONS_RES,
+ MOCK_AUTOCOMPLETE_PATH,
+ MOCK_PROJECT,
+ MOCK_SEARCH_CONTEXT,
+ MOCK_SEARCH_PATH,
+ MOCK_MR_PATH,
+ MOCK_ISSUE_PATH,
+} from '../mock_data';
jest.mock('~/flash');
@@ -12,10 +21,15 @@ describe('Header Search Store Actions', () => {
let state;
let mock;
- beforeEach(() => {
- state = createState({});
- mock = new MockAdapter(axios);
- });
+ const createState = (initialState) =>
+ initState({
+ searchPath: MOCK_SEARCH_PATH,
+ issuesPath: MOCK_ISSUE_PATH,
+ mrPath: MOCK_MR_PATH,
+ autocompletePath: MOCK_AUTOCOMPLETE_PATH,
+ searchContext: MOCK_SEARCH_CONTEXT,
+ ...initialState,
+ });
afterEach(() => {
state = null;
@@ -24,12 +38,14 @@ describe('Header Search Store Actions', () => {
describe.each`
axiosMock | type | expectedMutations
- ${{ method: 'onGet', code: 200, res: MOCK_AUTOCOMPLETE_OPTIONS_RES }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }]}
- ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]}
+ ${{ method: 'onGet', code: 200, res: MOCK_AUTOCOMPLETE_OPTIONS_RES }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }]}
+ ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]}
`('fetchAutocompleteOptions', ({ axiosMock, type, expectedMutations }) => {
describe(`on ${type}`, () => {
beforeEach(() => {
- mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res);
+ state = createState({});
+ mock = new MockAdapter(axios);
+ mock[axiosMock.method]().reply(axiosMock.code, axiosMock.res);
});
it(`should dispatch the correct mutations`, () => {
return testAction({
@@ -41,7 +57,35 @@ describe('Header Search Store Actions', () => {
});
});
+ describe.each`
+ project | ref | fetchType | expectedPath
+ ${null} | ${null} | ${null} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}`}
+ ${MOCK_PROJECT} | ${null} | ${'generic'} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_id=${MOCK_PROJECT.id}&filter=generic`}
+ ${null} | ${MOCK_PROJECT.id} | ${'generic'} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_ref=${MOCK_PROJECT.id}&filter=generic`}
+ ${MOCK_PROJECT} | ${MOCK_PROJECT.id} | ${'search'} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_id=${MOCK_PROJECT.id}&project_ref=${MOCK_PROJECT.id}&filter=search`}
+ `('autocompleteQuery', ({ project, ref, fetchType, expectedPath }) => {
+ describe(`when project is ${project?.name} and project ref is ${ref}`, () => {
+ beforeEach(() => {
+ state = createState({
+ search: MOCK_SEARCH,
+ searchContext: {
+ project,
+ ref,
+ },
+ });
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(actions.autocompleteQuery({ state, fetchType })).toBe(expectedPath);
+ });
+ });
+ });
+
describe('clearAutocomplete', () => {
+ beforeEach(() => {
+ state = createState({});
+ });
+
it('calls the CLEAR_AUTOCOMPLETE mutation', () => {
return testAction({
action: actions.clearAutocomplete,
@@ -52,6 +96,10 @@ describe('Header Search Store Actions', () => {
});
describe('setSearch', () => {
+ beforeEach(() => {
+ state = createState({});
+ });
+
it('calls the SET_SEARCH mutation', () => {
return testAction({
action: actions.setSearch,
diff --git a/spec/frontend/header_search/store/getters_spec.js b/spec/frontend/header_search/store/getters_spec.js
index c76be3c0360..a1d9481b5cc 100644
--- a/spec/frontend/header_search/store/getters_spec.js
+++ b/spec/frontend/header_search/store/getters_spec.js
@@ -73,30 +73,6 @@ describe('Header Search Store Getters', () => {
});
describe.each`
- project | ref | expectedPath
- ${null} | ${null} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}`}
- ${MOCK_PROJECT} | ${null} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_id=${MOCK_PROJECT.id}`}
- ${null} | ${MOCK_PROJECT.id} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_ref=${MOCK_PROJECT.id}`}
- ${MOCK_PROJECT} | ${MOCK_PROJECT.id} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_id=${MOCK_PROJECT.id}&project_ref=${MOCK_PROJECT.id}`}
- `('autocompleteQuery', ({ project, ref, expectedPath }) => {
- describe(`when project is ${project?.name} and project ref is ${ref}`, () => {
- beforeEach(() => {
- createState({
- searchContext: {
- project,
- ref,
- },
- });
- state.search = MOCK_SEARCH;
- });
-
- it(`should return ${expectedPath}`, () => {
- expect(getters.autocompleteQuery(state)).toBe(expectedPath);
- });
- });
- });
-
- describe.each`
group | group_metadata | project | project_metadata | expectedPath
${null} | ${null} | ${null} | ${null} | ${MOCK_ISSUE_PATH}
${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${null} | ${null} | ${'group/path'}
diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js
index 81c81fc0a9f..4406d14d990 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js
@@ -40,7 +40,7 @@ describe('Multi-file editor commit sidebar list', () => {
wrapper = mountComponent({ fileList: [] });
});
- it('renders no changes text ', () => {
+ it('renders no changes text', () => {
expect(wrapper.text()).toContain('No changes');
});
});
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 d899bc4f7d8..ee6ed694285 100644
--- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
@@ -1,6 +1,6 @@
import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
+import RadioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
import { createStore } from '~/ide/stores';
describe('IDE commit sidebar radio group', () => {
@@ -10,7 +10,7 @@ describe('IDE commit sidebar radio group', () => {
beforeEach(async () => {
store = createStore();
- const Component = Vue.extend(radioGroup);
+ const Component = Vue.extend(RadioGroup);
store.state.commit.commitAction = '2';
@@ -38,7 +38,7 @@ describe('IDE commit sidebar radio group', () => {
vm = new Vue({
components: {
- radioGroup,
+ RadioGroup,
},
store,
render: (createElement) =>
@@ -62,7 +62,7 @@ describe('IDE commit sidebar radio group', () => {
beforeEach(async () => {
vm.$destroy();
- const Component = Vue.extend(radioGroup);
+ const Component = Vue.extend(RadioGroup);
store.state.commit.commitAction = '1';
store.state.commit.newBranchName = 'test-123';
diff --git a/spec/frontend/ide/components/preview/navigator_spec.js b/spec/frontend/ide/components/preview/navigator_spec.js
index 532cb6e795c..043dcade858 100644
--- a/spec/frontend/ide/components/preview/navigator_spec.js
+++ b/spec/frontend/ide/components/preview/navigator_spec.js
@@ -76,7 +76,7 @@ describe('IDE clientside preview navigator', () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
await nextTick();
findBackButton().trigger('click');
- expect(findBackButton().attributes('disabled')).toBeFalsy();
+ expect(findBackButton().attributes()).not.toHaveProperty('disabled');
});
it('is disabled when there is no previous entry', async () => {
@@ -117,7 +117,7 @@ describe('IDE clientside preview navigator', () => {
findBackButton().trigger('click');
await nextTick();
- expect(findForwardButton().attributes('disabled')).toBeFalsy();
+ expect(findForwardButton().attributes()).not.toHaveProperty('disabled');
});
it('is disabled when there is no next entry', async () => {
diff --git a/spec/frontend/ide/components/shared/tokened_input_spec.js b/spec/frontend/ide/components/shared/tokened_input_spec.js
index a37c08af0a1..2efef9918b1 100644
--- a/spec/frontend/ide/components/shared/tokened_input_spec.js
+++ b/spec/frontend/ide/components/shared/tokened_input_spec.js
@@ -50,7 +50,7 @@ describe('IDE shared/TokenedInput', () => {
});
it('renders input', () => {
- expect(vm.$refs.input).toBeTruthy();
+ expect(vm.$refs.input).toBeInstanceOf(HTMLInputElement);
expect(vm.$refs.input).toHaveValue(TEST_VALUE);
});
diff --git a/spec/frontend/ide/init_gitlab_web_ide_spec.js b/spec/frontend/ide/init_gitlab_web_ide_spec.js
new file mode 100644
index 00000000000..ec8559f1b56
--- /dev/null
+++ b/spec/frontend/ide/init_gitlab_web_ide_spec.js
@@ -0,0 +1,62 @@
+import { start } from '@gitlab/web-ide';
+import { initGitlabWebIDE } from '~/ide/init_gitlab_web_ide';
+import { TEST_HOST } from 'helpers/test_constants';
+
+jest.mock('@gitlab/web-ide');
+
+const ROOT_ELEMENT_ID = 'ide';
+const TEST_NONCE = 'test123nonce';
+const TEST_PROJECT = { path_with_namespace: 'group1/project1' };
+const TEST_BRANCH_NAME = '12345-foo-patch';
+const TEST_GITLAB_URL = 'https://test-gitlab/';
+const TEST_GITLAB_WEB_IDE_PUBLIC_PATH = 'test/webpack/assets/gitlab-web-ide/public/path';
+
+describe('ide/init_gitlab_web_ide', () => {
+ const createRootElement = () => {
+ const el = document.createElement('div');
+
+ el.id = ROOT_ELEMENT_ID;
+ // why: We'll test that this class is removed later
+ el.classList.add('ide-loading');
+ el.dataset.project = JSON.stringify(TEST_PROJECT);
+ el.dataset.cspNonce = TEST_NONCE;
+ el.dataset.branchName = TEST_BRANCH_NAME;
+
+ document.body.append(el);
+ };
+ const findRootElement = () => document.getElementById(ROOT_ELEMENT_ID);
+ const act = () => initGitlabWebIDE(findRootElement());
+
+ beforeEach(() => {
+ process.env.GITLAB_WEB_IDE_PUBLIC_PATH = TEST_GITLAB_WEB_IDE_PUBLIC_PATH;
+ window.gon.gitlab_url = TEST_GITLAB_URL;
+
+ createRootElement();
+
+ act();
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ it('calls start with element', () => {
+ expect(start).toHaveBeenCalledWith(findRootElement(), {
+ baseUrl: `${TEST_HOST}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`,
+ projectPath: TEST_PROJECT.path_with_namespace,
+ ref: TEST_BRANCH_NAME,
+ gitlabUrl: TEST_GITLAB_URL,
+ nonce: TEST_NONCE,
+ });
+ });
+
+ it('clears classes and data from root element', () => {
+ const rootEl = findRootElement();
+
+ // why: Snapshot to test that `ide-loading` was removed and no other
+ // artifacts are remaining.
+ expect(rootEl.outerHTML).toBe(
+ '<div id="ide" class="gl--flex-center gl-relative gl-h-full"></div>',
+ );
+ });
+});
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index d1c31cd412b..38a54e569a9 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -78,7 +78,7 @@ describe('IDE store file actions', () => {
});
});
- it('switches to the next available file before closing the current one ', () => {
+ it('switches to the next available file before closing the current one', () => {
const f = file('newOpenFile');
store.state.openFiles.push(f);
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index d65039e89cc..4e8467de759 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -210,7 +210,7 @@ describe('IDE commit module actions', () => {
branch,
});
store.state.openFiles.forEach((entry) => {
- expect(entry.changed).toBeFalsy();
+ expect(entry.changed).toBe(false);
});
});
diff --git a/spec/frontend/import_entities/components/group_dropdown_spec.js b/spec/frontend/import_entities/components/group_dropdown_spec.js
index 1c1e1e7ebd4..b896437ecb2 100644
--- a/spec/frontend/import_entities/components/group_dropdown_spec.js
+++ b/spec/frontend/import_entities/components/group_dropdown_spec.js
@@ -42,7 +42,7 @@ describe('Import entities group dropdown component', () => {
createComponent({ namespaces });
namespacesTracker.mockReset();
- wrapper.find(GlSearchBoxByType).vm.$emit('input', 'match');
+ wrapper.findComponent(GlSearchBoxByType).vm.$emit('input', 'match');
await nextTick();
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 cdc508a0033..f97ea046cbe 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
@@ -99,7 +99,7 @@ describe('import table', () => {
});
await waitForPromises();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('does not renders loading icon when request is completed', async () => {
@@ -108,7 +108,7 @@ describe('import table', () => {
});
await waitForPromises();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
});
});
@@ -123,7 +123,7 @@ describe('import table', () => {
});
await waitForPromises();
- expect(wrapper.find(GlEmptyState).props().title).toBe(i18n.NO_GROUPS_FOUND);
+ expect(wrapper.findComponent(GlEmptyState).props().title).toBe(i18n.NO_GROUPS_FOUND);
});
});
@@ -268,7 +268,7 @@ describe('import table', () => {
});
it('correctly passes pagination info from query', () => {
- expect(wrapper.find(PaginationLinks).props().pageInfo).toStrictEqual(FAKE_PAGE_INFO);
+ expect(wrapper.findComponent(PaginationLinks).props().pageInfo).toStrictEqual(FAKE_PAGE_INFO);
});
it('renders pagination dropdown', () => {
@@ -293,7 +293,7 @@ describe('import table', () => {
it('updates page when page change is requested', async () => {
const REQUESTED_PAGE = 2;
- wrapper.find(PaginationLinks).props().change(REQUESTED_PAGE);
+ wrapper.findComponent(PaginationLinks).props().change(REQUESTED_PAGE);
await waitForPromises();
expect(bulkImportSourceGroupsQueryMock).toHaveBeenCalledWith(
@@ -316,7 +316,7 @@ describe('import table', () => {
},
versionValidation: FAKE_VERSION_VALIDATION,
});
- wrapper.find(PaginationLinks).props().change(REQUESTED_PAGE);
+ wrapper.findComponent(PaginationLinks).props().change(REQUESTED_PAGE);
await waitForPromises();
expect(wrapper.text()).toContain('Showing 21-21 of 38 groups that you own from');
@@ -539,8 +539,8 @@ describe('import table', () => {
});
await waitForPromises();
- expect(wrapper.find(GlAlert).exists()).toBe(true);
- expect(wrapper.find(GlAlert).text()).toContain('projects (require v14.8.0)');
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
+ expect(wrapper.findComponent(GlAlert).text()).toContain('projects (require v14.8.0)');
});
it('does not renders alert when there are no unavailable features', async () => {
@@ -558,7 +558,7 @@ describe('import table', () => {
});
await waitForPromises();
- expect(wrapper.find(GlAlert).exists()).toBe(false);
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
index d3f86672f33..18dc1217fec 100644
--- a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
@@ -22,8 +22,8 @@ describe('import target cell', () => {
let wrapper;
let group;
- const findNameInput = () => wrapper.find(GlFormInput);
- const findNamespaceDropdown = () => wrapper.find(ImportGroupDropdown);
+ const findNameInput = () => wrapper.findComponent(GlFormInput);
+ const findNamespaceDropdown = () => wrapper.findComponent(ImportGroupDropdown);
const createComponent = (props) => {
wrapper = shallowMount(ImportTargetCell, {
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 ea88c361f7b..9eae4ed974e 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
@@ -33,12 +33,12 @@ describe('BitbucketStatusTable', () => {
it('renders import table component', () => {
createComponent({ providerTitle: 'Test' });
- expect(wrapper.find(ImportProjectsTable).exists()).toBe(true);
+ expect(wrapper.findComponent(ImportProjectsTable).exists()).toBe(true);
});
it('passes alert in incompatible-repos-warning slot', () => {
createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub);
- expect(wrapper.find(GlAlert).exists()).toBe(true);
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
});
it('passes actions slot to import project table component', () => {
@@ -46,14 +46,14 @@ describe('BitbucketStatusTable', () => {
createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub, {
actions: actionsSlotContent,
});
- expect(wrapper.find(ImportProjectsTable).text()).toBe(actionsSlotContent);
+ expect(wrapper.findComponent(ImportProjectsTable).text()).toBe(actionsSlotContent);
});
it('dismisses alert when requested', async () => {
createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub);
- wrapper.find(GlAlert).vm.$emit('dismiss');
+ wrapper.findComponent(GlAlert).vm.$emit('dismiss');
await nextTick();
- expect(wrapper.find(GlAlert).exists()).toBe(false);
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
});
});
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 140fec3863b..c0ae4294e3d 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
@@ -30,10 +30,10 @@ describe('ImportProjectsTable', () => {
const findImportAllButton = () =>
wrapper
- .findAll(GlButton)
+ .findAllComponents(GlButton)
.filter((w) => w.props().variant === 'confirm')
.at(0);
- const findImportAllModal = () => wrapper.find({ ref: 'importAllModal' });
+ const findImportAllModal = () => wrapper.findComponent({ ref: 'importAllModal' });
const importAllFn = jest.fn();
const importAllModalShowFn = jest.fn();
@@ -89,13 +89,13 @@ describe('ImportProjectsTable', () => {
it('renders a loading icon while repos are loading', () => {
createComponent({ state: { isLoadingRepos: true } });
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders a loading icon while namespaces are loading', () => {
createComponent({ state: { isLoadingNamespaces: true } });
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders a table with provider repos', () => {
@@ -109,7 +109,7 @@ describe('ImportProjectsTable', () => {
state: { namespaces: [{ fullPath: 'path' }], repositories },
});
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find('table').exists()).toBe(true);
expect(
wrapper
@@ -118,7 +118,7 @@ describe('ImportProjectsTable', () => {
.exists(),
).toBe(true);
- expect(wrapper.findAll(ProviderRepoTableRow)).toHaveLength(repositories.length);
+ expect(wrapper.findAllComponents(ProviderRepoTableRow)).toHaveLength(repositories.length);
});
it.each`
@@ -170,7 +170,7 @@ describe('ImportProjectsTable', () => {
it('renders an empty state if there are no repositories available', () => {
createComponent({ state: { repositories: [] } });
- expect(wrapper.find(ProviderRepoTableRow).exists()).toBe(false);
+ expect(wrapper.findComponent(ProviderRepoTableRow).exists()).toBe(false);
expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`);
});
@@ -231,11 +231,11 @@ describe('ImportProjectsTable', () => {
});
it('renders intersection observer component', () => {
- expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true);
+ expect(wrapper.findComponent(GlIntersectionObserver).exists()).toBe(true);
});
it('calls fetchRepos when intersection observer appears', async () => {
- wrapper.find(GlIntersectionObserver).vm.$emit('appear');
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
await nextTick();
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 41a005199e1..17a07b1e9f9 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
@@ -74,11 +74,13 @@ describe('ProviderRepoTableRow', () => {
});
it('renders empty import status', () => {
- expect(wrapper.find(ImportStatus).props().status).toBe(STATUSES.NONE);
+ expect(wrapper.findComponent(ImportStatus).props().status).toBe(STATUSES.NONE);
});
it('renders a group namespace select', () => {
- expect(wrapper.find(ImportGroupDropdown).props().namespaces).toBe(availableNamespaces);
+ expect(wrapper.findComponent(ImportGroupDropdown).props().namespaces).toBe(
+ availableNamespaces,
+ );
});
it('renders import button', () => {
@@ -127,11 +129,13 @@ describe('ProviderRepoTableRow', () => {
});
it('renders proper import status', () => {
- expect(wrapper.find(ImportStatus).props().status).toBe(repo.importedProject.importStatus);
+ expect(wrapper.findComponent(ImportStatus).props().status).toBe(
+ repo.importedProject.importStatus,
+ );
});
it('does not renders a namespace select', () => {
- expect(wrapper.find(GlDropdown).exists()).toBe(false);
+ expect(wrapper.findComponent(GlDropdown).exists()).toBe(false);
});
it('does not render import button', () => {
@@ -139,7 +143,7 @@ describe('ProviderRepoTableRow', () => {
});
it('passes stats to import status component', () => {
- expect(wrapper.find(ImportStatus).props().stats).toBe(FAKE_STATS);
+ expect(wrapper.findComponent(ImportStatus).props().stats).toBe(FAKE_STATS);
});
});
@@ -165,7 +169,7 @@ describe('ProviderRepoTableRow', () => {
});
it('renders badge with error', () => {
- expect(wrapper.find(GlBadge).text()).toBe('Incompatible project');
+ expect(wrapper.findComponent(GlBadge).text()).toBe('Incompatible project');
});
});
});
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 55826b20ca3..110b692b222 100644
--- a/spec/frontend/import_entities/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/getters_spec.js
@@ -85,7 +85,7 @@ describe('import_projects store getters', () => {
});
describe('hasImportableRepos', () => {
- it('returns true if there are any importable projects ', () => {
+ it('returns true if there are any importable projects', () => {
localState.repositories = [IMPORTABLE_REPO, IMPORTED_REPO, INCOMPATIBLE_REPO];
expect(hasImportableRepos(localState)).toBe(true);
@@ -99,7 +99,7 @@ describe('import_projects store getters', () => {
});
describe('importAllCount', () => {
- it('returns count of available importable projects ', () => {
+ it('returns count of available importable projects', () => {
localState.repositories = [
IMPORTABLE_REPO,
IMPORTABLE_REPO,
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index 356480f931e..e8d222dc2e9 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -40,16 +40,16 @@ describe('Incidents List', () => {
all: 26,
};
- const findTable = () => wrapper.find(GlTable);
+ const findTable = () => wrapper.findComponent(GlTable);
const findTableRows = () => wrapper.findAll('table tbody tr');
- const findAlert = () => wrapper.find(GlAlert);
- const findLoader = () => wrapper.find(GlLoadingIcon);
- const findTimeAgo = () => wrapper.findAll(TimeAgoTooltip);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLoader = () => wrapper.findComponent(GlLoadingIcon);
+ const findTimeAgo = () => wrapper.findAllComponents(TimeAgoTooltip);
const findAssignees = () => wrapper.findAll('[data-testid="incident-assignees"]');
const findCreateIncidentBtn = () => wrapper.find('[data-testid="createIncidentBtn"]');
const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']");
- const findEmptyState = () => wrapper.find(GlEmptyState);
- const findSeverity = () => wrapper.findAll(SeverityToken);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findSeverity = () => wrapper.findAllComponents(SeverityToken);
const findEscalationStatus = () => wrapper.findAll('[data-testid="incident-escalation-status"]');
const findIncidentLink = () => wrapper.findByTestId('incident-link');
@@ -179,7 +179,7 @@ describe('Incidents List', () => {
});
it('renders an avatar component when there is an assignee', () => {
- const avatar = findAssignees().at(1).find(GlAvatar);
+ const avatar = findAssignees().at(1).findComponent(GlAvatar);
const { src, label } = avatar.attributes();
const { name, avatarUrl } = mockIncidents[1].assignees.nodes[0];
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 ff40f1fa008..394d1f12bcb 100644
--- a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
+++ b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
@@ -20,10 +20,10 @@ describe('IncidentsSettingTabs', () => {
}
});
- const findToggleButton = () => wrapper.find({ ref: 'toggleBtn' });
- const findSectionHeader = () => wrapper.find({ ref: 'sectionHeader' });
+ const findToggleButton = () => wrapper.findComponent({ ref: 'toggleBtn' });
+ const findSectionHeader = () => wrapper.findComponent({ ref: 'sectionHeader' });
- const findIntegrationTabs = () => wrapper.findAll(GlTab);
+ const findIntegrationTabs = () => wrapper.findAllComponents(GlTab);
it('renders header text', () => {
expect(findSectionHeader().text()).toBe('Incidents');
});
diff --git a/spec/frontend/integrations/edit/components/trigger_fields_spec.js b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
index 8ee55928926..c329ca8522f 100644
--- a/spec/frontend/integrations/edit/components/trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
@@ -24,7 +24,7 @@ describe('TriggerFields', () => {
});
const findTriggerLabel = () => wrapper.findByTestId('trigger-fields-group').find('label');
- const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAll(GlFormGroup);
+ const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAllComponents(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox);
const findAllGlFormInputs = () => wrapper.findAllComponents(GlFormInput);
@@ -86,7 +86,7 @@ describe('TriggerFields', () => {
expect(checkboxes).toHaveLength(2);
checkboxes.wrappers.forEach((checkbox, index) => {
- const checkBox = checkbox.find(GlFormCheckbox);
+ const checkBox = checkbox.findComponent(GlFormCheckbox);
expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText);
expect(checkbox.find('[type=hidden]').attributes('name')).toBe(
diff --git a/spec/frontend/invite_members/components/import_project_members_modal_spec.js b/spec/frontend/invite_members/components/import_project_members_modal_spec.js
index b4d42d90d99..8b2d13be309 100644
--- a/spec/frontend/invite_members/components/import_project_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/import_project_members_modal_spec.js
@@ -53,7 +53,7 @@ afterEach(() => {
describe('ImportProjectMembersModal', () => {
const findGlModal = () => wrapper.findComponent(GlModal);
- const findIntroText = () => wrapper.find({ ref: 'modalIntro' }).text();
+ const findIntroText = () => wrapper.findComponent({ ref: 'modalIntro' }).text();
const clickImportButton = () => findGlModal().vm.$emit('primary', { preventDefault: jest.fn() });
const closeModal = () => findGlModal().vm.$emit('hidden', { preventDefault: jest.fn() });
const findFormGroup = () => wrapper.findByTestId('form-group');
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 2058784b033..e9e1fbad07b 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -19,6 +19,7 @@ import {
MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT,
LEARN_GITLAB,
EXPANDED_ERRORS,
+ EMPTY_INVITES_ERROR_TEXT,
} from '~/invite_members/constants';
import eventHub from '~/invite_members/event_hub';
import ContentTransition from '~/vue_shared/components/content_transition.vue';
@@ -255,6 +256,8 @@ describe('InviteMembersModal', () => {
it('tracks the submit for invite_members_for_task', async () => {
await setupComponentWithTasks();
+ await triggerMembersTokenSelect([user1]);
+
clickInviteButton();
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name, {
@@ -265,6 +268,16 @@ describe('InviteMembersModal', () => {
INVITE_MEMBERS_FOR_TASK.submit,
);
});
+
+ it('does not track the submit for invite_members_for_task when invites have not been entered', async () => {
+ await setupComponentWithTasks();
+ clickInviteButton();
+
+ expect(ExperimentTracking).not.toHaveBeenCalledWith(
+ INVITE_MEMBERS_FOR_TASK.name,
+ expect.any,
+ );
+ });
});
});
@@ -380,6 +393,25 @@ describe('InviteMembersModal', () => {
"The member's email address is not allowed for this project. Go to the Admin area > Sign-up restrictions, and check Allowed domains for sign-ups.";
const expectedSyntaxError = 'email contains an invalid email address';
+ describe('when no invites have been entered in the form and then some are entered', () => {
+ beforeEach(async () => {
+ createInviteMembersToGroupWrapper();
+ });
+
+ it('displays an error', async () => {
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe(EMPTY_INVITES_ERROR_TEXT);
+ expect(findMembersSelect().props('exceptionState')).toBe(false);
+
+ await triggerMembersTokenSelect([user1]);
+
+ expect(membersFormGroupInvalidFeedback()).toBe('');
+ });
+ });
+
describe('when inviting an existing user to group by user ID', () => {
const postData = {
user_id: '1,2',
diff --git a/spec/frontend/invite_members/components/user_limit_notification_spec.js b/spec/frontend/invite_members/components/user_limit_notification_spec.js
index 543fc28a342..1ff2e86412f 100644
--- a/spec/frontend/invite_members/components/user_limit_notification_spec.js
+++ b/spec/frontend/invite_members/components/user_limit_notification_spec.js
@@ -1,12 +1,7 @@
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import UserLimitNotification from '~/invite_members/components/user_limit_notification.vue';
-
-import {
- REACHED_LIMIT_MESSAGE,
- REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE,
-} from '~/invite_members/constants';
-
+import { REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE } from '~/invite_members/constants';
import { freeUsersLimit, membersCount } from '../mock_data/member_modal';
const WARNING_ALERT_TITLE = 'You only have space for 2 more members in name';
@@ -52,22 +47,6 @@ describe('UserLimitNotification', () => {
});
});
- describe('when close to limit within a personal namepace', () => {
- beforeEach(() => {
- createComponent(true, false, { membersCount: 3, userNamespace: true });
- });
-
- it('renders the limit for a personal namespace', () => {
- const alert = findAlert();
-
- expect(alert.attributes('title')).toEqual(WARNING_ALERT_TITLE);
-
- expect(alert.text()).toEqual(
- 'To make more space, you can remove members who no longer need access.',
- );
- });
- });
-
describe('when close to limit within a group', () => {
it("renders user's limit notification", () => {
createComponent(true, false, { membersCount: 3 });
@@ -91,19 +70,5 @@ describe('UserLimitNotification', () => {
expect(alert.attributes('title')).toEqual("You've reached your 5 members limit for name");
expect(alert.text()).toEqual(REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE);
});
-
- describe('when free user namespace', () => {
- it("renders user's limit notification", () => {
- createComponent(true, true, { userNamespace: true });
-
- const alert = findAlert();
-
- expect(alert.attributes('title')).toEqual(
- "You've reached your 5 members limit for your personal projects",
- );
-
- expect(alert.text()).toEqual(REACHED_LIMIT_MESSAGE);
- });
- });
});
});
diff --git a/spec/frontend/issuable/components/issue_assignees_spec.js b/spec/frontend/issuable/components/issue_assignees_spec.js
index 713c8b1dfdd..9a33bfae240 100644
--- a/spec/frontend/issuable/components/issue_assignees_spec.js
+++ b/spec/frontend/issuable/components/issue_assignees_spec.js
@@ -27,7 +27,7 @@ describe('IssueAssigneesComponent', () => {
});
const findTooltipText = () => wrapper.find('.js-assignee-tooltip').text();
- const findAvatars = () => wrapper.findAll(UserAvatarLink);
+ const findAvatars = () => wrapper.findAllComponents(UserAvatarLink);
const findOverflowCounter = () => wrapper.find('.avatar-counter');
it('returns default data props', () => {
diff --git a/spec/frontend/issuable/components/issue_milestone_spec.js b/spec/frontend/issuable/components/issue_milestone_spec.js
index 9d67f602136..eac53c5f761 100644
--- a/spec/frontend/issuable/components/issue_milestone_spec.js
+++ b/spec/frontend/issuable/components/issue_milestone_spec.js
@@ -144,7 +144,7 @@ describe('IssueMilestoneComponent', () => {
});
it('renders milestone icon', () => {
- expect(wrapper.find(GlIcon).props('name')).toBe('clock');
+ expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock');
});
it('renders milestone title', () => {
diff --git a/spec/frontend/issuable/issuable_form_spec.js b/spec/frontend/issuable/issuable_form_spec.js
index d844f3394d5..5e67ea42b87 100644
--- a/spec/frontend/issuable/issuable_form_spec.js
+++ b/spec/frontend/issuable/issuable_form_spec.js
@@ -1,111 +1,200 @@
import $ from 'jquery';
+import Autosave from '~/autosave';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import IssuableForm from '~/issuable/issuable_form';
import setWindowLocation from 'helpers/set_window_location_helper';
+jest.mock('~/autosave');
+
+const createIssuable = (form) => {
+ return new IssuableForm(form);
+};
+
describe('IssuableForm', () => {
+ let $form;
let instance;
- const createIssuable = (form) => {
- instance = new IssuableForm(form);
- };
-
beforeEach(() => {
setHTMLFixture(`
<form>
<input name="[title]" />
+ <textarea name="[description]"></textarea>
</form>
`);
- createIssuable($('form'));
+ $form = $('form');
});
afterEach(() => {
resetHTMLFixture();
+ $form = null;
+ instance = null;
});
- describe('initAutosave', () => {
- it('creates autosave with the searchTerm included', () => {
- setWindowLocation('https://gitlab.test/foo?bar=true');
- const autosave = instance.initAutosave();
+ describe('autosave', () => {
+ let $title;
+ let $description;
+
+ beforeEach(() => {
+ $title = $form.find('input[name*="[title]"]');
+ $description = $form.find('textarea[name*="[description]"]');
+ });
- expect(autosave.key.includes('bar=true')).toBe(true);
+ afterEach(() => {
+ $title = null;
+ $description = null;
});
- it("creates autosave fields without the searchTerm if it's an issue new form", () => {
- setHTMLFixture(`
- <form data-new-issue-path="/issues/new">
- <input name="[title]" />
- </form>
- `);
- createIssuable($('form'));
+ describe('initAutosave', () => {
+ it('calls initAutosave', () => {
+ const initAutosave = jest.spyOn(IssuableForm.prototype, 'initAutosave');
+ createIssuable($form);
+
+ expect(initAutosave).toHaveBeenCalledTimes(1);
+ });
+
+ it('creates autosave with the searchTerm included', () => {
+ setWindowLocation('https://gitlab.test/foo?bar=true');
+ createIssuable($form);
+
+ expect(Autosave).toHaveBeenCalledWith(
+ $title,
+ ['/foo', 'bar=true', 'title'],
+ 'autosave//foo/bar=true=title',
+ );
+ expect(Autosave).toHaveBeenCalledWith(
+ $description,
+ ['/foo', 'bar=true', 'description'],
+ 'autosave//foo/bar=true=description',
+ );
+ });
+
+ it("creates autosave fields without the searchTerm if it's an issue new form", () => {
+ setWindowLocation('https://gitlab.test/issues/new?bar=true');
+ $form.attr('data-new-issue-path', '/issues/new');
+ createIssuable($form);
+
+ expect(Autosave).toHaveBeenCalledWith(
+ $title,
+ ['/issues/new', '', 'title'],
+ 'autosave//issues/new/bar=true=title',
+ );
+ expect(Autosave).toHaveBeenCalledWith(
+ $description,
+ ['/issues/new', '', 'description'],
+ 'autosave//issues/new/bar=true=description',
+ );
+ });
+
+ it.each([
+ {
+ id: 'confidential',
+ input: '<input type="checkbox" name="issue[confidential]"/>',
+ selector: 'input[name*="[confidential]"]',
+ },
+ {
+ id: 'due_date',
+ input: '<input type="text" name="issue[due_date]"/>',
+ selector: 'input[name*="[due_date]"]',
+ },
+ ])('creates $id autosave when $id input exist', ({ id, input, selector }) => {
+ $form.append(input);
+ const $input = $form.find(selector);
+ const totalAutosaveFormFields = $form.children().length;
+ createIssuable($form);
+
+ expect(Autosave).toHaveBeenCalledTimes(totalAutosaveFormFields);
+ expect(Autosave).toHaveBeenLastCalledWith($input, ['/', '', id], `autosave///=${id}`);
+ });
+ });
+
+ describe('resetAutosave', () => {
+ it('calls reset on title and description', () => {
+ instance = createIssuable($form);
+
+ instance.resetAutosave();
+
+ expect(instance.autosaves.get('title').reset).toHaveBeenCalledTimes(1);
+ expect(instance.autosaves.get('description').reset).toHaveBeenCalledTimes(1);
+ });
- setWindowLocation('https://gitlab.test/issues/new?bar=true');
+ it('resets autosave when submit', () => {
+ const resetAutosave = jest.spyOn(IssuableForm.prototype, 'resetAutosave');
+ createIssuable($form);
- const autosave = instance.initAutosave();
+ $form.submit();
- expect(autosave.key.includes('bar=true')).toBe(false);
+ expect(resetAutosave).toHaveBeenCalledTimes(1);
+ });
+
+ it('resets autosave on elements with the .js-reset-autosave class', () => {
+ const resetAutosave = jest.spyOn(IssuableForm.prototype, 'resetAutosave');
+ $form.append('<a class="js-reset-autosave">Cancel</a>');
+ createIssuable($form);
+
+ $form.find('.js-reset-autosave').trigger('click');
+
+ expect(resetAutosave).toHaveBeenCalledTimes(1);
+ });
+
+ it.each([
+ { id: 'confidential', input: '<input type="checkbox" name="issue[confidential]"/>' },
+ { id: 'due_date', input: '<input type="text" name="issue[due_date]"/>' },
+ ])('calls reset on autosave $id when $id input exist', ({ id, input }) => {
+ $form.append(input);
+ instance = createIssuable($form);
+ instance.resetAutosave();
+
+ expect(instance.autosaves.get(id).reset).toHaveBeenCalledTimes(1);
+ });
});
});
- describe('resetAutosave', () => {
- it('resets autosave on elements with the .js-reset-autosave class', () => {
- setHTMLFixture(`
- <form>
- <input name="[title]" />
- <textarea name="[description]"></textarea>
- <a class="js-reset-autosave">Cancel</a>
- </form>
- `);
- const $form = $('form');
- const resetAutosave = jest.spyOn(IssuableForm.prototype, 'resetAutosave');
- createIssuable($form);
-
- $form.find('.js-reset-autosave').trigger('click');
-
- expect(resetAutosave).toHaveBeenCalled();
+ describe('wip', () => {
+ beforeEach(() => {
+ instance = createIssuable($form);
});
- });
- describe('removeWip', () => {
- it.each`
- prefix
- ${'draFT: '}
- ${' [DRaft] '}
- ${'drAft:'}
- ${'[draFT]'}
- ${'(draft) '}
- ${' (DrafT)'}
- ${'draft: [draft] (draft)'}
- `('removes "$prefix" from the beginning of the title', ({ prefix }) => {
- instance.titleField.val(`${prefix}The Issuable's Title Value`);
-
- instance.removeWip();
-
- expect(instance.titleField.val()).toBe("The Issuable's Title Value");
+ describe('removeWip', () => {
+ it.each`
+ prefix
+ ${'draFT: '}
+ ${' [DRaft] '}
+ ${'drAft:'}
+ ${'[draFT]'}
+ ${'(draft) '}
+ ${' (DrafT)'}
+ ${'draft: [draft] (draft)'}
+ `('removes "$prefix" from the beginning of the title', ({ prefix }) => {
+ instance.titleField.val(`${prefix}The Issuable's Title Value`);
+
+ instance.removeWip();
+
+ expect(instance.titleField.val()).toBe("The Issuable's Title Value");
+ });
});
- });
- describe('addWip', () => {
- it("properly adds the work in progress prefix to the Issuable's title", () => {
- instance.titleField.val("The Issuable's Title Value");
+ describe('addWip', () => {
+ it("properly adds the work in progress prefix to the Issuable's title", () => {
+ instance.titleField.val("The Issuable's Title Value");
- instance.addWip();
+ instance.addWip();
- expect(instance.titleField.val()).toBe("Draft: The Issuable's Title Value");
+ expect(instance.titleField.val()).toBe("Draft: The Issuable's Title Value");
+ });
});
- });
- describe('workInProgress', () => {
- it.each`
- title | expected
- ${'draFT: something is happening'} | ${true}
- ${'draft something is happening'} | ${false}
- ${'something is happening to drafts'} | ${false}
- ${'something is happening'} | ${false}
- `('returns $expected with "$title"', ({ title, expected }) => {
- instance.titleField.val(title);
-
- expect(instance.workInProgress()).toBe(expected);
+ describe('workInProgress', () => {
+ it.each`
+ title | expected
+ ${'draFT: something is happening'} | ${true}
+ ${'draft something is happening'} | ${false}
+ ${'something is happening to drafts'} | ${false}
+ ${'something is happening'} | ${false}
+ `('returns $expected with "$title"', ({ title, expected }) => {
+ instance.titleField.val(title);
+
+ expect(instance.workInProgress()).toBe(expected);
+ });
});
});
});
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 d6aeacfe07a..bacebbade7f 100644
--- a/spec/frontend/issuable/related_issues/components/issue_token_spec.js
+++ b/spec/frontend/issuable/related_issues/components/issue_token_spec.js
@@ -31,11 +31,11 @@ describe('IssueToken', () => {
}
});
- const findLink = () => wrapper.find({ ref: 'link' });
- const findReference = () => wrapper.find({ ref: 'reference' });
+ const findLink = () => wrapper.findComponent({ ref: 'link' });
+ const findReference = () => wrapper.findComponent({ ref: 'reference' });
const findReferenceIcon = () => wrapper.find('[data-testid="referenceIcon"]');
const findRemoveBtn = () => wrapper.find('[data-testid="removeBtn"]');
- const findTitle = () => wrapper.find({ ref: 'title' });
+ const findTitle = () => wrapper.findComponent({ ref: 'title' });
describe('with reference supplied', () => {
beforeEach(() => {
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 772cc75a205..1b2935ce5d1 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
@@ -153,7 +153,7 @@ describe('RelatedIssuesBlock', () => {
});
it('sets `autoCompleteEpics` to false for add-issuable-form', () => {
- expect(wrapper.find(AddIssuableForm).props('autoCompleteEpics')).toBe(false);
+ expect(wrapper.findComponent(AddIssuableForm).props('autoCompleteEpics')).toBe(false);
});
});
@@ -227,7 +227,7 @@ describe('RelatedIssuesBlock', () => {
},
});
- const iconComponent = wrapper.find(GlIcon);
+ const iconComponent = wrapper.findComponent(GlIcon);
expect(iconComponent.exists()).toBe(true);
expect(iconComponent.props('name')).toBe(icon);
});
diff --git a/spec/frontend/issuable/related_issues/components/related_issues_list_spec.js b/spec/frontend/issuable/related_issues/components/related_issues_list_spec.js
index fd623ad9a5f..9bb71ec3dcb 100644
--- a/spec/frontend/issuable/related_issues/components/related_issues_list_spec.js
+++ b/spec/frontend/issuable/related_issues/components/related_issues_list_spec.js
@@ -187,7 +187,9 @@ describe('RelatedIssuesList', () => {
});
it('shows due date', () => {
- expect(wrapper.find(IssueDueDate).find('.board-card-info-text').text()).toBe('Nov 22, 2010');
+ expect(wrapper.findComponent(IssueDueDate).find('.board-card-info-text').text()).toBe(
+ 'Nov 22, 2010',
+ );
});
});
});
diff --git a/spec/frontend/issues/create_merge_request_dropdown_spec.js b/spec/frontend/issues/create_merge_request_dropdown_spec.js
index cb7173c56a8..cc2ee84348a 100644
--- a/spec/frontend/issues/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/issues/create_merge_request_dropdown_spec.js
@@ -106,7 +106,7 @@ describe('CreateMergeRequestDropdown', () => {
loading | hasClass
${true} | ${false}
${false} | ${true}
- `('it toggle loading spinner when loading is $loading', ({ loading, hasClass }) => {
+ `('toggle loading spinner when loading is $loading', ({ loading, hasClass }) => {
dropdown.setLoading(loading);
expect(document.querySelector('.js-spinner').classList.contains('gl-display-none')).toEqual(
diff --git a/spec/frontend/issues/list/components/issue_card_time_info_spec.js b/spec/frontend/issues/list/components/issue_card_time_info_spec.js
index c3f13ca6f9a..b0d3a63a8cf 100644
--- a/spec/frontend/issues/list/components/issue_card_time_info_spec.js
+++ b/spec/frontend/issues/list/components/issue_card_time_info_spec.js
@@ -21,7 +21,7 @@ describe('CE IssueCardTimeInfo component', () => {
};
const findMilestone = () => wrapper.find('[data-testid="issuable-milestone"]');
- const findMilestoneTitle = () => findMilestone().find(GlLink).attributes('title');
+ const findMilestoneTitle = () => findMilestone().findComponent(GlLink).attributes('title');
const findDueDate = () => wrapper.find('[data-testid="issuable-due-date"]');
const mountComponent = ({
@@ -56,8 +56,8 @@ describe('CE IssueCardTimeInfo component', () => {
const milestone = findMilestone();
expect(milestone.text()).toBe(issue.milestone.title);
- expect(milestone.find(GlIcon).props('name')).toBe('clock');
- expect(milestone.find(GlLink).attributes('href')).toBe(issue.milestone.webPath);
+ expect(milestone.findComponent(GlIcon).props('name')).toBe('clock');
+ expect(milestone.findComponent(GlLink).attributes('href')).toBe(issue.milestone.webPath);
});
describe.each`
@@ -84,7 +84,7 @@ describe('CE IssueCardTimeInfo component', () => {
expect(dueDate.text()).toBe('Dec 12, 2020');
expect(dueDate.attributes('title')).toBe('Due date');
- expect(dueDate.find(GlIcon).props('name')).toBe('calendar');
+ expect(dueDate.findComponent(GlIcon).props('name')).toBe('calendar');
expect(dueDate.classes()).not.toContain('gl-text-red-500');
});
});
@@ -118,6 +118,6 @@ describe('CE IssueCardTimeInfo component', () => {
expect(timeEstimate.text()).toBe(issue.humanTimeEstimate);
expect(timeEstimate.attributes('title')).toBe('Estimate');
- expect(timeEstimate.find(GlIcon).props('name')).toBe('timer');
+ expect(timeEstimate.findComponent(GlIcon).props('name')).toBe('timer');
});
});
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index a39853fd29c..5133c02b190 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -1,4 +1,4 @@
-import { GlButton, GlEmptyState, GlLink } from '@gitlab/ui';
+import { GlButton, GlEmptyState } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
@@ -29,7 +29,6 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro
import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
import IssuesListApp from '~/issues/list/components/issues_list_app.vue';
import NewIssueDropdown from '~/issues/list/components/new_issue_dropdown.vue';
-
import {
CREATED_DESC,
RELATIVE_POSITION,
@@ -58,6 +57,10 @@ import {
WORK_ITEM_TYPE_ENUM_TASK,
WORK_ITEM_TYPE_ENUM_TEST_CASE,
} from '~/work_items/constants';
+import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+
+import('~/issuable/bulk_update_sidebar');
+import('~/users_select');
jest.mock('@sentry/browser');
jest.mock('~/flash');
@@ -122,7 +125,6 @@ describe('CE IssuesListApp component', () => {
const findGlButtons = () => wrapper.findAllComponents(GlButton);
const findGlButtonAt = (index) => findGlButtons().at(index);
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
- const findGlLink = () => wrapper.findComponent(GlLink);
const findIssuableList = () => wrapper.findComponent(IssuableList);
const findNewIssueDropdown = () => wrapper.findComponent(NewIssueDropdown);
@@ -430,8 +432,9 @@ describe('CE IssuesListApp component', () => {
});
});
- it('is not set from url params', () => {
- expect(findIssuableList().props('initialFilterValue')).toEqual([]);
+ it('is set from url params and removes search terms', () => {
+ const expected = filteredTokens.filter((token) => token.type !== FILTERED_SEARCH_TERM);
+ expect(findIssuableList().props('initialFilterValue')).toEqual(expected);
});
it('shows an alert to tell the user they must be signed in to search', () => {
@@ -562,15 +565,16 @@ describe('CE IssuesListApp component', () => {
it('shows Jira integration information', () => {
const paragraphs = wrapper.findAll('p');
- expect(paragraphs.at(2).text()).toContain(IssuesListApp.i18n.jiraIntegrationTitle);
- expect(paragraphs.at(3).text()).toContain(
+ const links = wrapper.findAll('.gl-link');
+ expect(paragraphs.at(1).text()).toContain(IssuesListApp.i18n.jiraIntegrationTitle);
+ expect(paragraphs.at(2).text()).toContain(
'Enable the Jira integration to view your Jira issues in GitLab.',
);
- expect(paragraphs.at(4).text()).toContain(
+ expect(paragraphs.at(3).text()).toContain(
IssuesListApp.i18n.jiraIntegrationSecondaryMessage,
);
- expect(findGlLink().text()).toBe('Enable the Jira integration');
- expect(findGlLink().attributes('href')).toBe(defaultProvide.jiraIntegrationPath);
+ expect(links.at(1).text()).toBe('Enable the Jira integration');
+ expect(links.at(1).attributes('href')).toBe(defaultProvide.jiraIntegrationPath);
});
});
@@ -1006,8 +1010,9 @@ describe('CE IssuesListApp component', () => {
findIssuableList().vm.$emit('filter', filteredTokens);
});
- it('does not update url params', () => {
- expect(router.push).not.toHaveBeenCalled();
+ it('removes search terms', () => {
+ const expected = filteredTokens.filter((token) => token.type !== FILTERED_SEARCH_TERM);
+ expect(findIssuableList().props('initialFilterValue')).toEqual(expected);
});
it('shows an alert to tell the user they must be signed in to search', () => {
diff --git a/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js b/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js
index 2d773e8bf56..406b1fbc1af 100644
--- a/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js
+++ b/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js
@@ -11,9 +11,9 @@ describe('JiraIssuesImportStatus', () => {
};
let wrapper;
- const findAlert = () => wrapper.find(GlAlert);
+ const findAlert = () => wrapper.findComponent(GlAlert);
- const findAlertLabel = () => wrapper.find(GlAlert).find(GlLabel);
+ const findAlertLabel = () => wrapper.findComponent(GlAlert).findComponent(GlLabel);
const mountComponent = ({
shouldShowFinishedAlert = false,
@@ -49,7 +49,7 @@ describe('JiraIssuesImportStatus', () => {
});
it('does not show an alert', () => {
- expect(wrapper.find(GlAlert).exists()).toBe(false);
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
});
});
@@ -105,12 +105,12 @@ describe('JiraIssuesImportStatus', () => {
shouldShowInProgressAlert: true,
});
- expect(wrapper.find(GlAlert).exists()).toBe(true);
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
findAlert().vm.$emit('dismiss');
await nextTick();
- expect(wrapper.find(GlAlert).exists()).toBe(false);
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/issues/new/components/title_suggestions_item_spec.js b/spec/frontend/issues/new/components/title_suggestions_item_spec.js
index 5eb30b52de5..c54a762440f 100644
--- a/spec/frontend/issues/new/components/title_suggestions_item_spec.js
+++ b/spec/frontend/issues/new/components/title_suggestions_item_spec.js
@@ -20,7 +20,7 @@ describe('Issue title suggestions item component', () => {
}
const findLink = () => wrapper.findComponent(GlLink);
- const findAuthorLink = () => wrapper.findAll(GlLink).at(1);
+ const findAuthorLink = () => wrapper.findAllComponents(GlLink).at(1);
const findIcon = () => wrapper.findComponent(GlIcon);
const findTooltip = () => wrapper.findComponent(GlTooltip);
const findUserAvatar = () => wrapper.findComponent(UserAvatarImage);
@@ -105,7 +105,7 @@ describe('Issue title suggestions item component', () => {
const count = wrapper.findAll('.suggestion-counts span').at(0);
expect(count.text()).toContain('1');
- expect(count.find(GlIcon).props('name')).toBe('thumb-up');
+ expect(count.findComponent(GlIcon).props('name')).toBe('thumb-up');
});
it('renders notes count', () => {
@@ -114,7 +114,7 @@ describe('Issue title suggestions item component', () => {
const count = wrapper.findAll('.suggestion-counts span').at(1);
expect(count.text()).toContain('2');
- expect(count.find(GlIcon).props('name')).toBe('comment');
+ expect(count.findComponent(GlIcon).props('name')).toBe('comment');
});
});
diff --git a/spec/frontend/issues/new/components/title_suggestions_spec.js b/spec/frontend/issues/new/components/title_suggestions_spec.js
index 0a64890e4ca..1cd6576967a 100644
--- a/spec/frontend/issues/new/components/title_suggestions_spec.js
+++ b/spec/frontend/issues/new/components/title_suggestions_spec.js
@@ -83,7 +83,7 @@ describe('Issue title suggestions component', () => {
wrapper.setData(data);
await nextTick();
- expect(wrapper.findAll(TitleSuggestionsItem).length).toBe(2);
+ expect(wrapper.findAllComponents(TitleSuggestionsItem).length).toBe(2);
});
it('adds margin class to first item', async () => {
diff --git a/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js b/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
index 4df04cd5257..d30a8c081cc 100644
--- a/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
+++ b/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
@@ -65,9 +65,9 @@ describe('RelatedMergeRequests', () => {
describe('template', () => {
it('should render related merge request items', () => {
expect(wrapper.find('[data-testid="count"]').text()).toBe('2');
- expect(wrapper.findAll(RelatedIssuableItem)).toHaveLength(2);
+ expect(wrapper.findAllComponents(RelatedIssuableItem)).toHaveLength(2);
- const props = wrapper.findAll(RelatedIssuableItem).at(1).props();
+ const props = wrapper.findAllComponents(RelatedIssuableItem).at(1).props();
const data = mockData[1];
expect(props.idKey).toEqual(data.id);
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index 12f9707da04..3d027e2084c 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -461,7 +461,7 @@ describe('Issuable output', () => {
describe('when title is not in view', () => {
beforeEach(() => {
wrapper.vm.state.titleText = 'Sticky header title';
- wrapper.find(GlIntersectionObserver).vm.$emit('disappear');
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
});
it('shows with title', () => {
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index bdb1448148e..9d9abce887b 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -12,6 +12,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createFlash from '~/flash';
import Description from '~/issues/show/components/description.vue';
import { updateHistory } from '~/lib/utils/url_utility';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
@@ -71,7 +72,11 @@ describe('Description component', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
- function createComponent({ props = {}, provide } = {}) {
+ function createComponent({
+ props = {},
+ provide,
+ createWorkItemFromTaskHandler = createWorkItemFromTaskSuccessHandler,
+ } = {}) {
wrapper = shallowMountExtended(Description, {
propsData: {
issueId: 1,
@@ -85,7 +90,7 @@ describe('Description component', () => {
apolloProvider: createMockApollo([
[workItemQuery, queryHandler],
[workItemTypesQuery, workItemTypesQueryHandler],
- [createWorkItemFromTaskMutation, createWorkItemFromTaskSuccessHandler],
+ [createWorkItemFromTaskMutation, createWorkItemFromTaskHandler],
]),
mocks: {
$toast,
@@ -317,7 +322,28 @@ describe('Description component', () => {
expect(findModal().exists()).toBe(false);
});
+ it('shows toast after delete success', async () => {
+ const newDesc = 'description';
+ findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
+
+ expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
+ expect($toast.show).toHaveBeenCalledWith('Task deleted');
+ });
+ });
+
+ describe('creating work item from checklist item', () => {
it('emits `updateDescription` after creating new work item', async () => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithCheckboxes,
+ },
+ provide: {
+ glFeatures: {
+ workItemsCreateFromMarkdown: true,
+ },
+ },
+ });
+
const newDescription = `<p>New description</p>`;
await findConvertToTaskButton().trigger('click');
@@ -327,12 +353,28 @@ describe('Description component', () => {
expect(wrapper.emitted('updateDescription')).toEqual([[newDescription]]);
});
- it('shows toast after delete success', async () => {
- const newDesc = 'description';
- findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
+ it('shows flash message when creating task fails', async () => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithCheckboxes,
+ },
+ provide: {
+ glFeatures: {
+ workItemsCreateFromMarkdown: true,
+ },
+ },
+ createWorkItemFromTaskHandler: jest.fn().mockRejectedValue({}),
+ });
- expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
- expect($toast.show).toHaveBeenCalledWith('Task deleted');
+ await findConvertToTaskButton().trigger('click');
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalledWith(
+ expect.objectContaining({
+ message: 'Something went wrong when creating task. Please try again.',
+ }),
+ );
});
});
diff --git a/spec/frontend/issues/show/components/edit_actions_spec.js b/spec/frontend/issues/show/components/edit_actions_spec.js
index d58bf1be812..11c43ea4388 100644
--- a/spec/frontend/issues/show/components/edit_actions_spec.js
+++ b/spec/frontend/issues/show/components/edit_actions_spec.js
@@ -2,16 +2,9 @@ import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { mockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
import IssuableEditActions from '~/issues/show/components/edit_actions.vue';
-import DeleteIssueModal from '~/issues/show/components/delete_issue_modal.vue';
import eventHub from '~/issues/show/event_hub';
-import {
- getIssueStateQueryResponse,
- updateIssueStateQueryResponse,
-} from '../mock_data/apollo_mock';
describe('Edit Actions component', () => {
let wrapper;
@@ -31,8 +24,6 @@ describe('Edit Actions component', () => {
},
};
- const modalId = 'delete-issuable-modal-1';
-
const createComponent = ({ props, data } = {}) => {
fakeApollo = createMockApollo([], mockResolvers);
@@ -50,16 +41,13 @@ describe('Edit Actions component', () => {
data() {
return {
issueState: {},
- modalId,
...data,
};
},
});
};
- const findModal = () => wrapper.findComponent(DeleteIssueModal);
const findEditButtons = () => wrapper.findAllComponents(GlButton);
- const findDeleteButton = () => wrapper.findByTestId('issuable-delete-button');
const findSaveButton = () => wrapper.findByTestId('issuable-save-button');
const findCancelButton = () => wrapper.findByTestId('issuable-cancel-button');
@@ -79,23 +67,12 @@ describe('Edit Actions component', () => {
});
});
- it('does not render the delete button if canDestroy is false', () => {
- createComponent({ props: { canDestroy: false } });
- expect(findDeleteButton().exists()).toBe(false);
- });
-
it('disables save button when title is blank', () => {
createComponent({ props: { formState: { title: '', issue_type: '' } } });
expect(findSaveButton().attributes('disabled')).toBe('true');
});
- it('does not render the delete button if showDeleteButton is false', () => {
- createComponent({ props: { showDeleteButton: false } });
-
- expect(findDeleteButton().exists()).toBe(false);
- });
-
describe('updateIssuable', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
@@ -119,63 +96,4 @@ describe('Edit Actions component', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('close.form');
});
});
-
- describe('delete issue button', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- it('tracks clicking on button', () => {
- findDeleteButton().vm.$emit('click');
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
- label: 'delete_issue',
- });
- });
- });
-
- describe('delete issue modal', () => {
- it('renders', () => {
- expect(findModal().props()).toEqual({
- issuePath: 'gitlab-org/gitlab-test/-/issues/1',
- issueType: 'Issue',
- modalId,
- title: 'Delete issue',
- });
- });
- });
-
- describe('deleteIssuable', () => {
- beforeEach(() => {
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- });
-
- it('does not send the `delete.issuable` event when clicking delete button', () => {
- findDeleteButton().vm.$emit('click');
- expect(eventHub.$emit).not.toHaveBeenCalled();
- });
-
- it('sends the `delete.issuable` event when clicking the delete confirm button', async () => {
- expect(eventHub.$emit).toHaveBeenCalledTimes(0);
- findModal().vm.$emit('delete');
- expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable');
- expect(eventHub.$emit).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('with Apollo cache mock', () => {
- it('renders the right delete button text per apollo cache type', async () => {
- mockIssueStateData.mockResolvedValue(getIssueStateQueryResponse);
- await waitForPromises();
- expect(findDeleteButton().text()).toBe('Delete issue');
- });
-
- it('should not change the delete button text per apollo cache mutation', async () => {
- mockIssueStateData.mockResolvedValue(updateIssueStateQueryResponse);
- await waitForPromises();
- expect(findDeleteButton().text()).toBe('Delete issue');
- });
- });
});
diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js
index d0e33f0b980..61433607a2b 100644
--- a/spec/frontend/issues/show/components/fields/description_spec.js
+++ b/spec/frontend/issues/show/components/fields/description_spec.js
@@ -6,7 +6,7 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
describe('Description field component', () => {
let wrapper;
- const findTextarea = () => wrapper.find({ ref: 'textarea' });
+ const findTextarea = () => wrapper.findComponent({ ref: 'textarea' });
const mountComponent = (description = 'test') =>
shallowMount(DescriptionField, {
diff --git a/spec/frontend/issues/show/components/fields/title_spec.js b/spec/frontend/issues/show/components/fields/title_spec.js
index de04405d89b..a5fa96d8d64 100644
--- a/spec/frontend/issues/show/components/fields/title_spec.js
+++ b/spec/frontend/issues/show/components/fields/title_spec.js
@@ -5,7 +5,7 @@ import eventHub from '~/issues/show/event_hub';
describe('Title field component', () => {
let wrapper;
- const findInput = () => wrapper.find({ ref: 'input' });
+ const findInput = () => wrapper.findComponent({ ref: 'input' });
beforeEach(() => {
jest.spyOn(eventHub, '$emit');
diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js
index 329c4234f30..dc2b3c6fc48 100644
--- a/spec/frontend/issues/show/components/header_actions_spec.js
+++ b/spec/frontend/issues/show/components/header_actions_spec.js
@@ -65,17 +65,17 @@ describe('HeaderActions component', () => {
},
};
- const findToggleIssueStateButton = () => wrapper.find(GlButton);
+ const findToggleIssueStateButton = () => wrapper.findComponent(GlButton);
const findDropdownBy = (dataTestId) => wrapper.find(`[data-testid="${dataTestId}"]`);
const findMobileDropdown = () => findDropdownBy('mobile-dropdown');
const findDesktopDropdown = () => findDropdownBy('desktop-dropdown');
- const findMobileDropdownItems = () => findMobileDropdown().findAll(GlDropdownItem);
- const findDesktopDropdownItems = () => findDesktopDropdown().findAll(GlDropdownItem);
+ const findMobileDropdownItems = () => findMobileDropdown().findAllComponents(GlDropdownItem);
+ const findDesktopDropdownItems = () => findDesktopDropdown().findAllComponents(GlDropdownItem);
- const findModal = () => wrapper.find(GlModal);
+ const findModal = () => wrapper.findComponent(GlModal);
- const findModalLinkAt = (index) => findModal().findAll(GlLink).at(index);
+ const findModalLinkAt = (index) => findModal().findAllComponents(GlLink).at(index);
const mountComponent = ({
props = {},
diff --git a/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js
index 3ab2bb3460b..1286617d64a 100644
--- a/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js
@@ -1,13 +1,13 @@
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlDatepicker } from '@gitlab/ui';
-import { __, s__ } from '~/locale';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CreateTimelineEvent from '~/issues/show/components/incidents/create_timeline_event.vue';
import TimelineEventsForm from '~/issues/show/components/incidents/timeline_events_form.vue';
import createTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/create_timeline_event.mutation.graphql';
import getTimelineEvents from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql';
+import { timelineFormI18n } from '~/issues/show/components/incidents/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createAlert } from '~/flash';
import { useFakeDate } from 'helpers/fake_date';
@@ -35,24 +35,21 @@ describe('Create Timeline events', () => {
let responseSpy;
let apolloProvider;
- const findSubmitButton = () => wrapper.findByText(__('Save'));
- const findSubmitAndAddButton = () =>
- wrapper.findByText(s__('Incident|Save and add another event'));
- const findCancelButton = () => wrapper.findByText(__('Cancel'));
+ const findSubmitButton = () => wrapper.findByText(timelineFormI18n.save);
+ const findSubmitAndAddButton = () => wrapper.findByText(timelineFormI18n.saveAndAdd);
+ const findCancelButton = () => wrapper.findByText(timelineFormI18n.cancel);
const findDatePicker = () => wrapper.findComponent(GlDatepicker);
const findNoteInput = () => wrapper.findByTestId('input-note');
const setNoteInput = () => {
- const textarea = findNoteInput().element;
- textarea.value = mockInputData.note;
- textarea.dispatchEvent(new Event('input'));
+ findNoteInput().setValue(mockInputData.note);
};
const findHourInput = () => wrapper.findByTestId('input-hours');
const findMinuteInput = () => wrapper.findByTestId('input-minutes');
const setDatetime = () => {
const inputDate = new Date(mockInputData.occurredAt);
findDatePicker().vm.$emit('input', inputDate);
- findHourInput().vm.$emit('input', inputDate.getHours());
- findMinuteInput().vm.$emit('input', inputDate.getMinutes());
+ findHourInput().setValue(inputDate.getHours());
+ findMinuteInput().setValue(inputDate.getMinutes());
};
const fillForm = () => {
setDatetime();
diff --git a/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js b/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js
new file mode 100644
index 00000000000..4c1638a9147
--- /dev/null
+++ b/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js
@@ -0,0 +1,44 @@
+import EditTimelineEvent from '~/issues/show/components/incidents/edit_timeline_event.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import TimelineEventsForm from '~/issues/show/components/incidents/timeline_events_form.vue';
+
+import { mockEvents, fakeEventData, mockInputData } from './mock_data';
+
+describe('Edit Timeline events', () => {
+ let wrapper;
+
+ const mountComponent = () => {
+ wrapper = mountExtended(EditTimelineEvent, {
+ propsData: {
+ event: mockEvents[0],
+ editTimelineEventActive: false,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ const findTimelineEventsForm = () => wrapper.findComponent(TimelineEventsForm);
+
+ const mockSaveData = { ...fakeEventData, ...mockInputData };
+
+ describe('editTimelineEvent', () => {
+ const saveEventEvent = { 'handle-save-edit': [[mockSaveData, false]] };
+
+ it('should call the mutation with the right variables', async () => {
+ await findTimelineEventsForm().vm.$emit('save-event', mockSaveData, false);
+
+ expect(wrapper.emitted()).toEqual(saveEventEvent);
+ });
+
+ it('should close the form on cancel', async () => {
+ const cancelEvent = { 'hide-edit': [[]] };
+
+ await findTimelineEventsForm().vm.$emit('cancel');
+
+ expect(wrapper.emitted()).toEqual(cancelEvent);
+ });
+ });
+});
diff --git a/spec/frontend/issues/show/components/incidents/highlight_bar_spec.js b/spec/frontend/issues/show/components/incidents/highlight_bar_spec.js
index 155ae703e48..1cfb7d12a91 100644
--- a/spec/frontend/issues/show/components/incidents/highlight_bar_spec.js
+++ b/spec/frontend/issues/show/components/incidents/highlight_bar_spec.js
@@ -41,7 +41,7 @@ describe('Highlight Bar', () => {
}
});
- const findLink = () => wrapper.find(GlLink);
+ const findLink = () => wrapper.findComponent(GlLink);
describe('empty state', () => {
beforeEach(() => {
diff --git a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
index 8e090645be2..d92aeabba0f 100644
--- a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
+++ b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
@@ -61,12 +61,12 @@ describe('Incident Tabs component', () => {
);
};
- const findTabs = () => wrapper.findAll(GlTab);
+ const findTabs = () => wrapper.findAllComponents(GlTab);
const findSummaryTab = () => findTabs().at(0);
const findAlertDetailsTab = () => wrapper.find('[data-testid="alert-details-tab"]');
- const findAlertDetailsComponent = () => wrapper.find(AlertDetailsTable);
- const findDescriptionComponent = () => wrapper.find(DescriptionComponent);
- const findHighlightBarComponent = () => wrapper.find(HighlightBar);
+ const findAlertDetailsComponent = () => wrapper.findComponent(AlertDetailsTable);
+ const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent);
+ const findHighlightBarComponent = () => wrapper.findComponent(HighlightBar);
const findTimelineTab = () => wrapper.findComponent(TimelineTab);
describe('empty state', () => {
diff --git a/spec/frontend/issues/show/components/incidents/mock_data.js b/spec/frontend/issues/show/components/incidents/mock_data.js
index 75c0a7350ae..adea2b6df59 100644
--- a/spec/frontend/issues/show/components/incidents/mock_data.js
+++ b/spec/frontend/issues/show/components/incidents/mock_data.js
@@ -49,6 +49,15 @@ export const mockEvents = [
},
];
+const mockUpdatedEvent = {
+ id: 'gid://gitlab/IncidentManagement::TimelineEvent/8',
+ note: 'another one23',
+ noteHtml: '<p>another one23</p>',
+ action: 'comment',
+ occurredAt: '2022-07-01T12:47:00Z',
+ createdAt: '2022-07-20T12:47:40Z',
+};
+
export const timelineEventsQueryListResponse = {
data: {
project: {
@@ -93,6 +102,29 @@ export const timelineEventsCreateEventError = {
},
};
+export const timelineEventsEditEventResponse = {
+ data: {
+ timelineEventUpdate: {
+ timelineEvent: {
+ ...mockUpdatedEvent,
+ },
+ errors: [],
+ __typename: 'TimelineEventUpdatePayload',
+ },
+ },
+};
+
+export const timelineEventsEditEventError = {
+ data: {
+ timelineEventUpdate: {
+ timelineEvent: {
+ ...mockUpdatedEvent,
+ },
+ errors: ['Create error'],
+ },
+ },
+};
+
const timelineEventDeleteData = (errors = []) => {
return {
data: {
@@ -125,3 +157,13 @@ export const mockGetTimelineData = {
},
},
};
+
+export const fakeDate = '2020-07-08T00:00:00.000Z';
+
+export const mockInputData = {
+ note: 'test',
+ occurredAt: '2020-08-10T02:30:00.000Z',
+};
+
+const { id, note, occurredAt } = mockEvents[0];
+export const fakeEventData = { id, note, occurredAt };
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
index cd2cbb63246..7f086a276f7 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
@@ -4,6 +4,8 @@ import { GlDatepicker } from '@gitlab/ui';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import TimelineEventsForm from '~/issues/show/components/incidents/timeline_events_form.vue';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { timelineFormI18n } from '~/issues/show/components/incidents/constants';
import { createAlert } from '~/flash';
import { useFakeDate } from 'helpers/fake_date';
@@ -13,6 +15,8 @@ jest.mock('~/flash');
const fakeDate = '2020-07-08T00:00:00.000Z';
+const mockInputDate = new Date('2021-08-12');
+
describe('Timeline events form', () => {
// July 8 2020
useFakeDate(fakeDate);
@@ -21,7 +25,7 @@ describe('Timeline events form', () => {
const mountComponent = ({ mountMethod = shallowMountExtended }) => {
wrapper = mountMethod(TimelineEventsForm, {
propsData: {
- hasTimelineEvents: true,
+ showSaveAndAdd: true,
isEventProcessed: false,
},
});
@@ -32,17 +36,17 @@ describe('Timeline events form', () => {
wrapper.destroy();
});
- const findSubmitButton = () => wrapper.findByText('Save');
- const findSubmitAndAddButton = () => wrapper.findByText('Save and add another event');
- const findCancelButton = () => wrapper.findByText('Cancel');
+ const findMarkdownField = () => wrapper.findComponent(MarkdownField);
+ const findSubmitButton = () => wrapper.findByText(timelineFormI18n.save);
+ const findSubmitAndAddButton = () => wrapper.findByText(timelineFormI18n.saveAndAdd);
+ const findCancelButton = () => wrapper.findByText(timelineFormI18n.cancel);
const findDatePicker = () => wrapper.findComponent(GlDatepicker);
- const findDatePickerInput = () => wrapper.findByTestId('input-datepicker');
const findHourInput = () => wrapper.findByTestId('input-hours');
const findMinuteInput = () => wrapper.findByTestId('input-minutes');
const setDatetime = () => {
- findDatePicker().vm.$emit('input', new Date('2021-08-12'));
- findHourInput().vm.$emit('input', 5);
- findMinuteInput().vm.$emit('input', 45);
+ findDatePicker().vm.$emit('input', mockInputDate);
+ findHourInput().setValue(5);
+ findMinuteInput().setValue(45);
};
const submitForm = async () => {
@@ -58,6 +62,22 @@ describe('Timeline events form', () => {
await waitForPromises();
};
+ it('renders markdown-field component with correct list of toolbar items', () => {
+ mountComponent({ mountMethod: mountExtended });
+
+ expect(findMarkdownField().props('restrictedToolBarItems')).toEqual([
+ 'quote',
+ 'strikethrough',
+ 'bullet-list',
+ 'numbered-list',
+ 'task-list',
+ 'collapsible-section',
+ 'table',
+ 'attach-file',
+ 'full-screen',
+ ]);
+ });
+
describe('form button behaviour', () => {
beforeEach(() => {
mountComponent({ mountMethod: mountExtended });
@@ -87,14 +107,14 @@ describe('Timeline events form', () => {
setDatetime();
await nextTick();
- expect(findDatePickerInput().element.value).toBe('2021-08-12');
+ expect(findDatePicker().props('value')).toBe(mockInputDate);
expect(findHourInput().element.value).toBe('5');
expect(findMinuteInput().element.value).toBe('45');
wrapper.vm.clear();
await nextTick();
- expect(findDatePickerInput().element.value).toBe('2020-07-08');
+ expect(findDatePicker().props('value')).toStrictEqual(new Date(fakeDate));
expect(findHourInput().element.value).toBe('0');
expect(findMinuteInput().element.value).toBe('0');
});
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js
index 90e55003ab3..1bf8d68efd4 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js
@@ -1,6 +1,7 @@
import timezoneMock from 'timezone-mock';
import { GlIcon, GlDropdown } from '@gitlab/ui';
import { nextTick } from 'vue';
+import { timelineItemI18n } from '~/issues/show/components/incidents/constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import IncidentTimelineEventItem from '~/issues/show/components/incidents/timeline_events_item.vue';
import { mockEvents } from './mock_data';
@@ -15,21 +16,19 @@ describe('IncidentTimelineEventList', () => {
action,
noteHtml,
occurredAt,
- isLastItem: false,
...propsData,
},
provide: {
- canUpdate: false,
+ canUpdateTimelineEvent: false,
...provide,
},
});
};
const findCommentIcon = () => wrapper.findComponent(GlIcon);
- const findTextContainer = () => wrapper.findByTestId('event-text-container');
const findEventTime = () => wrapper.findByTestId('event-time');
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDeleteButton = () => wrapper.findByText('Delete');
+ const findDeleteButton = () => wrapper.findByText(timelineItemI18n.delete);
describe('template', () => {
it('shows comment icon', () => {
@@ -50,20 +49,6 @@ describe('IncidentTimelineEventList', () => {
expect(findEventTime().text()).toBe('15:59 UTC');
});
- describe('last item in list', () => {
- it('shows a bottom border when not the last item', () => {
- mountComponent();
-
- expect(findTextContainer().classes()).toContain('gl-border-1');
- });
-
- it('does not show a bottom border when the last item', () => {
- mountComponent({ propsData: { isLastItem: true } });
-
- expect(wrapper.classes()).not.toContain('gl-border-1');
- });
- });
-
describe.each`
timezone
${'Europe/London'}
@@ -96,20 +81,20 @@ describe('IncidentTimelineEventList', () => {
});
it('shows dropdown and delete item when user has update permission', () => {
- mountComponent({ provide: { canUpdate: true } });
+ mountComponent({ provide: { canUpdateTimelineEvent: true } });
expect(findDropdown().exists()).toBe(true);
expect(findDeleteButton().exists()).toBe(true);
});
it('triggers a delete when the delete button is clicked', async () => {
- mountComponent({ provide: { canUpdate: true } });
+ mountComponent({ provide: { canUpdateTimelineEvent: true } });
findDeleteButton().trigger('click');
await nextTick();
- expect(wrapper.emitted().delete).toBeTruthy();
+ expect(wrapper.emitted().delete).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
index 4d2d53c990e..dff1c429d07 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
@@ -3,16 +3,24 @@ import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import IncidentTimelineEventList from '~/issues/show/components/incidents/timeline_events_list.vue';
-import IncidentTimelineEventListItem from '~/issues/show/components/incidents/timeline_events_item.vue';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import IncidentTimelineEventItem from '~/issues/show/components/incidents/timeline_events_item.vue';
+import EditTimelineEvent from '~/issues/show/components/incidents/edit_timeline_event.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import deleteTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/delete_timeline_event.mutation.graphql';
+import editTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/edit_timeline_event.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { useFakeDate } from 'helpers/fake_date';
import { createAlert } from '~/flash';
import {
mockEvents,
timelineEventsDeleteEventResponse,
timelineEventsDeleteEventError,
+ timelineEventsEditEventResponse,
+ timelineEventsEditEventError,
+ fakeDate,
+ fakeEventData,
+ mockInputData,
} from './mock_data';
Vue.use(VueApollo);
@@ -20,83 +28,73 @@ Vue.use(VueApollo);
jest.mock('~/flash');
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
-const deleteEventResponse = jest.fn();
-
-function createMockApolloProvider() {
- deleteEventResponse.mockResolvedValue(timelineEventsDeleteEventResponse);
- const requestHandlers = [[deleteTimelineEventMutation, deleteEventResponse]];
- return createMockApollo(requestHandlers);
-}
-
const mockConfirmAction = ({ confirmed }) => {
confirmAction.mockResolvedValueOnce(confirmed);
};
describe('IncidentTimelineEventList', () => {
+ useFakeDate(fakeDate);
let wrapper;
+ const deleteResponseSpy = jest.fn().mockResolvedValue(timelineEventsDeleteEventResponse);
+ const editResponseSpy = jest.fn().mockResolvedValue(timelineEventsEditEventResponse);
- const mountComponent = (mockApollo) => {
- const apollo = mockApollo ? { apolloProvider: mockApollo } : {};
+ const requestHandlers = [
+ [deleteTimelineEventMutation, deleteResponseSpy],
+ [editTimelineEventMutation, editResponseSpy],
+ ];
+ const apolloProvider = createMockApollo(requestHandlers);
- wrapper = shallowMountExtended(IncidentTimelineEventList, {
+ const mountComponent = () => {
+ wrapper = mountExtended(IncidentTimelineEventList, {
+ propsData: {
+ timelineEvents: mockEvents,
+ },
provide: {
fullPath: 'group/project',
issuableId: '1',
+ canUpdateTimelineEvent: true,
},
- propsData: {
- timelineEvents: mockEvents,
- },
- ...apollo,
+ apolloProvider,
});
};
const findTimelineEventGroups = () => wrapper.findAllByTestId('timeline-group');
- const findItems = (base = wrapper) => base.findAll(IncidentTimelineEventListItem);
+ const findItems = (base = wrapper) => base.findAllComponents(IncidentTimelineEventItem);
const findFirstTimelineEventGroup = () => findTimelineEventGroups().at(0);
const findSecondTimelineEventGroup = () => findTimelineEventGroups().at(1);
const findDates = () => wrapper.findAllByTestId('event-date');
const clickFirstDeleteButton = async () => {
- findItems()
- .at(0)
- .vm.$emit('delete', { ...mockEvents[0] });
+ findItems().at(0).vm.$emit('delete', { fakeEventData });
await waitForPromises();
};
+ const clickFirstEditButton = async () => {
+ findItems().at(0).vm.$emit('edit');
+ await waitForPromises();
+ };
+ beforeEach(() => {
+ mountComponent();
+ });
+
afterEach(() => {
- confirmAction.mockReset();
- deleteEventResponse.mockReset();
wrapper.destroy();
});
describe('template', () => {
it('groups items correctly', () => {
- mountComponent();
-
expect(findTimelineEventGroups()).toHaveLength(2);
expect(findItems(findFirstTimelineEventGroup())).toHaveLength(1);
expect(findItems(findSecondTimelineEventGroup())).toHaveLength(2);
});
- it('sets the isLastItem prop correctly', () => {
- mountComponent();
-
- expect(findItems().at(0).props('isLastItem')).toBe(false);
- expect(findItems().at(1).props('isLastItem')).toBe(false);
- expect(findItems().at(2).props('isLastItem')).toBe(true);
- });
-
it('sets the event props correctly', () => {
- mountComponent();
-
expect(findItems().at(1).props('occurredAt')).toBe(mockEvents[1].occurredAt);
expect(findItems().at(1).props('action')).toBe(mockEvents[1].action);
expect(findItems().at(1).props('noteHtml')).toBe(mockEvents[1].noteHtml);
});
it('formats dates correctly', () => {
- mountComponent();
-
expect(findDates().at(0).text()).toBe('2022-03-22');
expect(findDates().at(1).text()).toBe('2022-03-23');
});
@@ -110,8 +108,6 @@ describe('IncidentTimelineEventList', () => {
describe(timezone, () => {
beforeEach(() => {
timezoneMock.register(timezone);
-
- mountComponent();
});
afterEach(() => {
@@ -131,12 +127,9 @@ describe('IncidentTimelineEventList', () => {
it('should delete when button is clicked', async () => {
const expectedVars = { input: { id: mockEvents[0].id } };
-
- mountComponent(createMockApolloProvider());
-
await clickFirstDeleteButton();
- expect(deleteEventResponse).toHaveBeenCalledWith(expectedVars);
+ expect(deleteResponseSpy).toHaveBeenCalledWith(expectedVars);
});
it('should show an error when delete returns an error', async () => {
@@ -144,8 +137,7 @@ describe('IncidentTimelineEventList', () => {
message: 'Error deleting incident timeline event: Item does not exist',
};
- mountComponent(createMockApolloProvider());
- deleteEventResponse.mockResolvedValue(timelineEventsDeleteEventError);
+ deleteResponseSpy.mockResolvedValue(timelineEventsDeleteEventError);
await clickFirstDeleteButton();
@@ -158,8 +150,7 @@ describe('IncidentTimelineEventList', () => {
error: new Error(),
message: 'Something went wrong while deleting the incident timeline event.',
};
- mountComponent(createMockApolloProvider());
- deleteEventResponse.mockRejectedValueOnce();
+ deleteResponseSpy.mockRejectedValueOnce();
await clickFirstDeleteButton();
@@ -167,4 +158,76 @@ describe('IncidentTimelineEventList', () => {
});
});
});
+
+ describe('Edit Functionality', () => {
+ beforeEach(() => {
+ mountComponent();
+ clickFirstEditButton();
+ });
+
+ const findEditEvent = () => wrapper.findComponent(EditTimelineEvent);
+ const mockSaveData = { ...fakeEventData, ...mockInputData };
+
+ describe('editTimelineEvent', () => {
+ it('should call the mutation with the right variables', async () => {
+ await findEditEvent().vm.$emit('handle-save-edit', mockSaveData);
+ await waitForPromises();
+
+ expect(editResponseSpy).toHaveBeenCalledWith({
+ input: mockSaveData,
+ });
+ });
+
+ it('should close the form on successful addition', async () => {
+ await findEditEvent().vm.$emit('handle-save-edit', mockSaveData);
+ await waitForPromises();
+
+ expect(findEditEvent().exists()).toBe(false);
+ });
+
+ it('should close the form on cancel', async () => {
+ await findEditEvent().vm.$emit('hide-edit');
+ await waitForPromises();
+
+ expect(findEditEvent().exists()).toBe(false);
+ });
+ });
+
+ describe('error handling', () => {
+ it('should show an error when submission returns an error', async () => {
+ const expectedAlertArgs = {
+ message: `Error updating incident timeline event: ${timelineEventsEditEventError.data.timelineEventUpdate.errors[0]}`,
+ };
+ editResponseSpy.mockResolvedValueOnce(timelineEventsEditEventError);
+
+ await findEditEvent().vm.$emit('handle-save-edit', mockSaveData);
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith(expectedAlertArgs);
+ });
+
+ it('should show an error when submission fails', async () => {
+ const expectedAlertArgs = {
+ captureError: true,
+ error: new Error(),
+ message: 'Something went wrong while updating the incident timeline event.',
+ };
+ editResponseSpy.mockRejectedValueOnce();
+
+ await findEditEvent().vm.$emit('handle-save-edit', mockSaveData);
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith(expectedAlertArgs);
+ });
+
+ it('should keep the form open on failed addition', async () => {
+ editResponseSpy.mockResolvedValueOnce(timelineEventsEditEventError);
+
+ await findEditEvent().vm.$emit('handle-save-edit', mockSaveData);
+ await waitForPromises();
+
+ expect(findEditEvent().exists()).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js
index 2cdb971395d..5bac1d6e7ad 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js
@@ -36,7 +36,7 @@ describe('TimelineEventsTab', () => {
provide: {
fullPath: 'group/project',
issuableId: '1',
- canUpdate: true,
+ canUpdateTimelineEvent: true,
...provide,
},
apolloProvider: mockApollo,
@@ -136,29 +136,20 @@ describe('TimelineEventsTab', () => {
it('should not show a button when user cannot update', () => {
mountComponent({
mockApollo: createMockApolloProvider(emptyResponse),
- provide: { canUpdate: false },
+ provide: { canUpdateTimelineEvent: false },
});
expect(findAddEventButton().exists()).toBe(false);
});
it('should not show a form by default', () => {
- expect(findCreateTimelineEvent().isVisible()).toBe(false);
+ expect(findCreateTimelineEvent().exists()).toBe(false);
});
it('should show a form when button is clicked', async () => {
await findAddEventButton().trigger('click');
- expect(findCreateTimelineEvent().isVisible()).toBe(true);
- });
-
- it('should clear the form when button is clicked', async () => {
- const mockClear = jest.fn();
- wrapper.vm.$refs.createEventForm.clearForm = mockClear;
-
- await findAddEventButton().trigger('click');
-
- expect(mockClear).toHaveBeenCalled();
+ expect(findCreateTimelineEvent().exists()).toBe(true);
});
it('should hide the form when the hide event is emitted', async () => {
@@ -167,7 +158,7 @@ describe('TimelineEventsTab', () => {
await findCreateTimelineEvent().vm.$emit('hide-new-timeline-events-form');
- expect(findCreateTimelineEvent().isVisible()).toBe(false);
+ expect(findCreateTimelineEvent().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/issues/show/components/incidents/utils_spec.js b/spec/frontend/issues/show/components/incidents/utils_spec.js
index d3a86680f14..f0494591e95 100644
--- a/spec/frontend/issues/show/components/incidents/utils_spec.js
+++ b/spec/frontend/issues/show/components/incidents/utils_spec.js
@@ -2,7 +2,7 @@ import timezoneMock from 'timezone-mock';
import {
displayAndLogError,
getEventIcon,
- getUtcShiftedDateNow,
+ getUtcShiftedDate,
} from '~/issues/show/components/incidents/utils';
import { createAlert } from '~/flash';
@@ -34,7 +34,7 @@ describe('incident utils', () => {
});
});
- describe('getUtcShiftedDateNow', () => {
+ describe('getUtcShiftedDate', () => {
beforeEach(() => {
timezoneMock.register('US/Pacific');
});
@@ -46,7 +46,7 @@ describe('incident utils', () => {
it('should shift the date by the timezone offset', () => {
const date = new Date();
- const shiftedDate = getUtcShiftedDateNow();
+ const shiftedDate = getUtcShiftedDate();
expect(shiftedDate > date).toBe(true);
});
diff --git a/spec/frontend/issues/show/components/pinned_links_spec.js b/spec/frontend/issues/show/components/pinned_links_spec.js
index aac720df6e9..208baac7124 100644
--- a/spec/frontend/issues/show/components/pinned_links_spec.js
+++ b/spec/frontend/issues/show/components/pinned_links_spec.js
@@ -9,7 +9,7 @@ const plainStatusUrl = 'https://status.com';
describe('PinnedLinks', () => {
let wrapper;
- const findButtons = () => wrapper.findAll(GlButton);
+ const findButtons = () => wrapper.findAllComponents(GlButton);
const createComponent = (props) => {
wrapper = shallowMount(PinnedLinks, {
diff --git a/spec/frontend/issues/show/components/sentry_error_stack_trace_spec.js b/spec/frontend/issues/show/components/sentry_error_stack_trace_spec.js
index b38d2b60057..d4202f4a6ab 100644
--- a/spec/frontend/issues/show/components/sentry_error_stack_trace_spec.js
+++ b/spec/frontend/issues/show/components/sentry_error_stack_trace_spec.js
@@ -62,8 +62,8 @@ describe('Sentry Error Stack Trace', () => {
describe('loading', () => {
it('should show spinner while loading', () => {
mountComponent();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
- expect(wrapper.find(Stacktrace).exists()).toBe(false);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(Stacktrace).exists()).toBe(false);
});
});
@@ -74,8 +74,8 @@ describe('Sentry Error Stack Trace', () => {
it('should show stacktrace', () => {
mountComponent({ stubs: {} });
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.find(Stacktrace).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(Stacktrace).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js b/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
index b9fed5f34f1..cc8346253ee 100644
--- a/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
+++ b/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
@@ -217,7 +217,7 @@ describe('NewBranchForm', () => {
});
it('emits `success` event', () => {
- expect(wrapper.emitted('success')).toBeTruthy();
+ expect(wrapper.emitted('success')).toHaveLength(1);
});
it('called `createBranch` mutation correctly', () => {
diff --git a/spec/frontend/jira_connect/subscriptions/api_spec.js b/spec/frontend/jira_connect/subscriptions/api_spec.js
index 57b11bdbc27..cf496d5836a 100644
--- a/spec/frontend/jira_connect/subscriptions/api_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/api_spec.js
@@ -1,7 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
-import { addSubscription, removeSubscription, fetchGroups } from '~/jira_connect/subscriptions/api';
+import {
+ axiosInstance,
+ addSubscription,
+ removeSubscription,
+ fetchGroups,
+ getCurrentUser,
+ addJiraConnectSubscription,
+ updateInstallation,
+} from '~/jira_connect/subscriptions/api';
import { getJwt } from '~/jira_connect/subscriptions/utils';
-import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
jest.mock('~/jira_connect/subscriptions/utils', () => ({
@@ -9,21 +16,26 @@ jest.mock('~/jira_connect/subscriptions/utils', () => ({
}));
describe('JiraConnect API', () => {
- let mock;
+ let axiosMock;
+ let originalGon;
let response;
const mockAddPath = 'addPath';
const mockRemovePath = 'removePath';
const mockNamespace = 'namespace';
const mockJwt = 'jwt';
+ const mockAccessToken = 'accessToken';
const mockResponse = { success: true };
beforeEach(() => {
- mock = new MockAdapter(axios);
+ axiosMock = new MockAdapter(axiosInstance);
+ originalGon = window.gon;
+ window.gon = { api_version: 'v4' };
});
afterEach(() => {
- mock.restore();
+ axiosMock.restore();
+ window.gon = originalGon;
response = null;
});
@@ -31,8 +43,8 @@ describe('JiraConnect API', () => {
const makeRequest = () => addSubscription(mockAddPath, mockNamespace);
it('returns success response', async () => {
- jest.spyOn(axios, 'post');
- mock
+ jest.spyOn(axiosInstance, 'post');
+ axiosMock
.onPost(mockAddPath, {
jwt: mockJwt,
namespace_path: mockNamespace,
@@ -42,7 +54,7 @@ describe('JiraConnect API', () => {
response = await makeRequest();
expect(getJwt).toHaveBeenCalled();
- expect(axios.post).toHaveBeenCalledWith(mockAddPath, {
+ expect(axiosInstance.post).toHaveBeenCalledWith(mockAddPath, {
jwt: mockJwt,
namespace_path: mockNamespace,
});
@@ -54,13 +66,13 @@ describe('JiraConnect API', () => {
const makeRequest = () => removeSubscription(mockRemovePath);
it('returns success response', async () => {
- jest.spyOn(axios, 'delete');
- mock.onDelete(mockRemovePath).replyOnce(httpStatus.OK, mockResponse);
+ jest.spyOn(axiosInstance, 'delete');
+ axiosMock.onDelete(mockRemovePath).replyOnce(httpStatus.OK, mockResponse);
response = await makeRequest();
expect(getJwt).toHaveBeenCalled();
- expect(axios.delete).toHaveBeenCalledWith(mockRemovePath, {
+ expect(axiosInstance.delete).toHaveBeenCalledWith(mockRemovePath, {
params: {
jwt: mockJwt,
},
@@ -81,8 +93,8 @@ describe('JiraConnect API', () => {
});
it('returns success response', async () => {
- jest.spyOn(axios, 'get');
- mock
+ jest.spyOn(axiosInstance, 'get');
+ axiosMock
.onGet(mockGroupsPath, {
page: mockPage,
per_page: mockPerPage,
@@ -91,7 +103,7 @@ describe('JiraConnect API', () => {
response = await makeRequest();
- expect(axios.get).toHaveBeenCalledWith(mockGroupsPath, {
+ expect(axiosInstance.get).toHaveBeenCalledWith(mockGroupsPath, {
params: {
page: mockPage,
per_page: mockPerPage,
@@ -100,4 +112,82 @@ describe('JiraConnect API', () => {
expect(response.data).toEqual(mockResponse);
});
});
+
+ describe('getCurrentUser', () => {
+ const makeRequest = () => getCurrentUser();
+
+ it('returns success response', async () => {
+ const expectedUrl = '/api/v4/user';
+
+ jest.spyOn(axiosInstance, 'get');
+
+ axiosMock.onGet(expectedUrl).replyOnce(httpStatus.OK, mockResponse);
+
+ response = await makeRequest();
+
+ expect(axiosInstance.get).toHaveBeenCalledWith(expectedUrl, {});
+ expect(response.data).toEqual(mockResponse);
+ });
+ });
+
+ describe('addJiraConnectSubscription', () => {
+ const makeRequest = () =>
+ addJiraConnectSubscription(mockNamespace, { jwt: mockJwt, accessToken: mockAccessToken });
+
+ it('returns success response', async () => {
+ const expectedUrl = '/api/v4/integrations/jira_connect/subscriptions';
+
+ jest.spyOn(axiosInstance, 'post');
+
+ axiosMock.onPost(expectedUrl).replyOnce(httpStatus.OK, mockResponse);
+
+ response = await makeRequest();
+
+ expect(axiosInstance.post).toHaveBeenCalledWith(
+ expectedUrl,
+ {
+ jwt: mockJwt,
+ namespace_path: mockNamespace,
+ },
+ { headers: { Authorization: `Bearer ${mockAccessToken}` } },
+ );
+ expect(response.data).toEqual(mockResponse);
+ });
+ });
+
+ describe('updateInstallation', () => {
+ const expectedUrl = '/-/jira_connect/installations';
+
+ it.each`
+ instanceUrl | expectedInstanceUrl
+ ${'https://gitlab.com'} | ${null}
+ ${'https://gitlab.mycompany.com'} | ${'https://gitlab.mycompany.com'}
+ `(
+ 'when instanceUrl is $instanceUrl, it passes `instance_url` as $expectedInstanceUrl',
+ async ({ instanceUrl, expectedInstanceUrl }) => {
+ const makeRequest = () => updateInstallation(instanceUrl);
+
+ jest.spyOn(axiosInstance, 'put');
+ axiosMock
+ .onPut(expectedUrl, {
+ jwt: mockJwt,
+ installation: {
+ instance_url: expectedInstanceUrl,
+ },
+ })
+ .replyOnce(httpStatus.OK, mockResponse);
+
+ response = await makeRequest();
+
+ expect(getJwt).toHaveBeenCalled();
+ expect(axiosInstance.put).toHaveBeenCalledWith(expectedUrl, {
+ jwt: mockJwt,
+ installation: {
+ instance_url: expectedInstanceUrl,
+ },
+ });
+ expect(response.data).toEqual(mockResponse);
+ },
+ );
+ });
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
index d871b1e1dcc..f1fc5e4d90b 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
@@ -50,7 +50,7 @@ describe('GroupsList', () => {
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findAllItems = () => wrapper.findAll(GroupsListItem);
+ const findAllItems = () => wrapper.findAllComponents(GroupsListItem);
const findFirstItem = () => findAllItems().at(0);
const findSecondItem = () => findAllItems().at(1);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index 9894141be5a..369ddda8dbe 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -31,8 +31,8 @@ describe('JiraConnectApp', () => {
const findUserLink = () => wrapper.findComponent(UserLink);
const findBrowserSupportAlert = () => wrapper.findComponent(BrowserSupportAlert);
- const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => {
- store = createStore({ subscriptions: [mockSubscription] });
+ const createComponent = ({ provide, mountFn = shallowMountExtended, initialState = {} } = {}) => {
+ store = createStore({ ...initialState, subscriptions: [mockSubscription] });
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = mountFn(JiraConnectApp, {
@@ -60,7 +60,7 @@ describe('JiraConnectApp', () => {
});
it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
- expect(findSignInPage().exists()).toBe(shouldRenderSignInPage);
+ expect(findSignInPage().isVisible()).toBe(shouldRenderSignInPage);
if (shouldRenderSignInPage) {
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
}
@@ -133,7 +133,7 @@ describe('JiraConnectApp', () => {
});
it('renders link when `linkUrl` is set', async () => {
- createComponent({ mountFn: mountExtended });
+ createComponent({ provide: { usersPath: '' }, mountFn: mountExtended });
store.commit(SET_ALERT, {
message: __('test message %{linkStart}test link%{linkEnd}'),
@@ -211,21 +211,22 @@ describe('JiraConnectApp', () => {
describe('when `jiraConnectOauth` feature flag is enabled', () => {
const mockSubscriptionsPath = '/mockSubscriptionsPath';
- beforeEach(() => {
+ beforeEach(async () => {
jest.spyOn(api, 'fetchSubscriptions').mockResolvedValue({ data: { subscriptions: [] } });
+ jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(true);
createComponent({
+ initialState: {
+ currentUser: { name: 'root' },
+ },
provide: {
glFeatures: { jiraConnectOauth: true },
subscriptionsPath: mockSubscriptionsPath,
},
});
- });
- describe('when component mounts', () => {
- it('dispatches `fetchSubscriptions` action', async () => {
- expect(store.dispatch).toHaveBeenCalledWith('fetchSubscriptions', mockSubscriptionsPath);
- });
+ findSignInPage().vm.$emit('sign-in-oauth');
+ await nextTick();
});
describe('when oauth button emits `sign-in-oauth` event', () => {
diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
index ed0abaaf576..01317eb5dba 100644
--- a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
@@ -1,39 +1,41 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
+
import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
import {
I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
OAUTH_WINDOW_OPTIONS,
} from '~/jira_connect/subscriptions/constants';
-import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
-import httpStatus from '~/lib/utils/http_status';
import AccessorUtilities from '~/lib/utils/accessor';
-import { getCurrentUser } from '~/rest_api';
+import {
+ getCurrentUser,
+ fetchOAuthApplicationId,
+ fetchOAuthToken,
+} from '~/jira_connect/subscriptions/api';
import createStore from '~/jira_connect/subscriptions/store';
import { SET_ACCESS_TOKEN } from '~/jira_connect/subscriptions/store/mutation_types';
jest.mock('~/lib/utils/accessor');
jest.mock('~/jira_connect/subscriptions/utils');
jest.mock('~/jira_connect/subscriptions/api');
-jest.mock('~/rest_api');
jest.mock('~/jira_connect/subscriptions/pkce', () => ({
createCodeVerifier: jest.fn().mockReturnValue('mock-verifier'),
createCodeChallenge: jest.fn().mockResolvedValue('mock-challenge'),
}));
-const mockOauthMetadata = {
- oauth_authorize_url: 'https://gitlab.com/mockOauth',
- oauth_token_url: 'https://gitlab.com/mockOauthToken',
- state: 'good-state',
-};
-
describe('SignInOauthButton', () => {
let wrapper;
- let mockAxios;
let store;
+ const mockOauthMetadata = {
+ oauth_authorize_url: 'https://gitlab.com/mockOauth',
+ oauth_token_path: 'https://gitlab.com/mockOauthToken',
+ oauth_token_payload: {
+ client_id: '543678901',
+ },
+ state: 'good-state',
+ };
const createComponent = ({ slots, props } = {}) => {
store = createStore();
@@ -50,13 +52,8 @@ describe('SignInOauthButton', () => {
});
};
- beforeEach(() => {
- mockAxios = new MockAdapter(axios);
- });
-
afterEach(() => {
wrapper.destroy();
- mockAxios.restore();
});
const findButton = () => wrapper.findComponent(GlButton);
@@ -69,6 +66,46 @@ describe('SignInOauthButton', () => {
expect(findButton().props('category')).toBe('primary');
});
+ describe('when `gitlabBasePath` is passed', () => {
+ const mockBasePath = 'https://gitlab.mycompany.com';
+
+ it('uses custom text for button', () => {
+ createComponent({
+ props: {
+ gitlabBasePath: mockBasePath,
+ },
+ });
+
+ expect(findButton().text()).toBe(`Sign in to ${mockBasePath}`);
+ });
+
+ describe('on click', () => {
+ const mockClientId = '798412381';
+
+ beforeEach(async () => {
+ fetchOAuthApplicationId.mockReturnValue({ data: { application_id: mockClientId } });
+ jest.spyOn(window, 'open').mockReturnValue();
+ createComponent({
+ props: {
+ gitlabBasePath: mockBasePath,
+ },
+ });
+
+ findButton().vm.$emit('click');
+
+ await nextTick();
+ });
+
+ it('calls `window.open` with correct arguments', () => {
+ expect(window.open).toHaveBeenCalledWith(
+ `${mockBasePath}/mockOauth?code_challenge=mock-challenge&code_challenge_method=S256&client_id=${mockClientId}`,
+ I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
+ OAUTH_WINDOW_OPTIONS,
+ );
+ });
+ });
+ });
+
it.each`
scenario | cryptoAvailable
${'when crypto API is available'} | ${true}
@@ -96,7 +133,7 @@ describe('SignInOauthButton', () => {
it('calls `window.open` with correct arguments', () => {
expect(window.open).toHaveBeenCalledWith(
- `${mockOauthMetadata.oauth_authorize_url}?code_challenge=mock-challenge&code_challenge_method=S256`,
+ `${mockOauthMetadata.oauth_authorize_url}?code_challenge=mock-challenge&code_challenge_method=S256&client_id=${mockOauthMetadata.oauth_token_payload.client_id}`,
I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
OAUTH_WINDOW_OPTIONS,
);
@@ -151,11 +188,7 @@ describe('SignInOauthButton', () => {
describe('when API requests succeed', () => {
beforeEach(async () => {
- jest.spyOn(axios, 'post');
- jest.spyOn(axios, 'get');
- mockAxios
- .onPost(mockOauthMetadata.oauth_token_url)
- .replyOnce(httpStatus.OK, { access_token: mockAccessToken });
+ fetchOAuthToken.mockResolvedValue({ data: { access_token: mockAccessToken } });
getCurrentUser.mockResolvedValue({ data: mockUser });
window.dispatchEvent(new MessageEvent('message', mockEvent));
@@ -164,9 +197,10 @@ describe('SignInOauthButton', () => {
});
it('executes POST request to Oauth token endpoint', () => {
- expect(axios.post).toHaveBeenCalledWith(mockOauthMetadata.oauth_token_url, {
+ expect(fetchOAuthToken).toHaveBeenCalledWith(mockOauthMetadata.oauth_token_path, {
code: '1234',
code_verifier: 'mock-verifier',
+ client_id: mockOauthMetadata.oauth_token_payload.client_id,
});
});
@@ -185,10 +219,7 @@ describe('SignInOauthButton', () => {
describe('when API requests fail', () => {
beforeEach(async () => {
- jest.spyOn(axios, 'post');
- mockAxios
- .onPost(mockOauthMetadata.oauth_token_url)
- .replyOnce(httpStatus.INTERNAL_SERVER_ERROR);
+ fetchOAuthToken.mockRejectedValue();
window.dispatchEvent(new MessageEvent('message', mockEvent));
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js
index 1649920b48b..b9a8451f3b3 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js
@@ -101,7 +101,7 @@ describe('SignInGitlabCom', () => {
const button = findSignInOauthButton();
button.vm.$emit('error');
- expect(wrapper.emitted('error')).toBeTruthy();
+ expect(wrapper.emitted('error')).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
index f4be8bf121d..10696d25f17 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
@@ -5,9 +5,22 @@ import SignInGitlabMultiversion from '~/jira_connect/subscriptions/pages/sign_in
import VersionSelectForm from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue';
import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
+import { updateInstallation } from '~/jira_connect/subscriptions/api';
+import { reloadPage, persistBaseUrl, retrieveBaseUrl } from '~/jira_connect/subscriptions/utils';
+
+jest.mock('~/jira_connect/subscriptions/api', () => {
+ return {
+ updateInstallation: jest.fn(),
+ setApiBaseURL: jest.fn(),
+ };
+});
+jest.mock('~/jira_connect/subscriptions/utils');
+
describe('SignInGitlabMultiversion', () => {
let wrapper;
+ const mockBasePath = 'gitlab.mycompany.com';
+
const findVersionSelectForm = () => wrapper.findComponent(VersionSelectForm);
const findSignInOauthButton = () => wrapper.findComponent(SignInOauthButton);
const findSubtitle = () => wrapper.findByTestId('subtitle');
@@ -29,30 +42,32 @@ describe('SignInGitlabMultiversion', () => {
});
describe('when form emits "submit" event', () => {
- it('hides the version select form and shows the sign in button', async () => {
+ it('updates the backend, then saves the baseUrl and reloads', async () => {
+ updateInstallation.mockResolvedValue({});
+
createComponent();
- findVersionSelectForm().vm.$emit('submit', 'gitlab.mycompany.com');
+ findVersionSelectForm().vm.$emit('submit', mockBasePath);
await nextTick();
- expect(findVersionSelectForm().exists()).toBe(false);
- expect(findSignInOauthButton().exists()).toBe(true);
+ expect(updateInstallation).toHaveBeenCalled();
+ expect(persistBaseUrl).toHaveBeenCalledWith(mockBasePath);
+ expect(reloadPage).toHaveBeenCalled();
});
});
});
});
describe('when version is selected', () => {
- beforeEach(async () => {
+ beforeEach(() => {
+ retrieveBaseUrl.mockReturnValue(mockBasePath);
createComponent();
-
- findVersionSelectForm().vm.$emit('submit', 'gitlab.mycompany.com');
- await nextTick();
});
describe('sign in button', () => {
it('renders sign in button', () => {
expect(findSignInOauthButton().exists()).toBe(true);
+ expect(findSignInOauthButton().props('gitlabBasePath')).toBe(mockBasePath);
});
describe('when button emits `sign-in` event', () => {
@@ -71,7 +86,7 @@ describe('SignInGitlabMultiversion', () => {
const button = findSignInOauthButton();
button.vm.$emit('error');
- expect(wrapper.emitted('error')).toBeTruthy();
+ expect(wrapper.emitted('error')).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/store/actions_spec.js b/spec/frontend/jira_connect/subscriptions/store/actions_spec.js
index 53b5d8e70af..5e3c30269b5 100644
--- a/spec/frontend/jira_connect/subscriptions/store/actions_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/store/actions_spec.js
@@ -8,8 +8,6 @@ import {
} from '~/jira_connect/subscriptions/store/actions';
import state from '~/jira_connect/subscriptions/store/state';
import * as api from '~/jira_connect/subscriptions/api';
-import * as userApi from '~/api/user_api';
-import * as integrationsApi from '~/api/integrations_api';
import {
I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE,
I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
@@ -79,7 +77,7 @@ describe('JiraConnect actions', () => {
describe('when API request succeeds', () => {
it('commits the SET_ACCESS_TOKEN and SET_CURRENT_USER mutations', async () => {
const mockUser = { name: 'root' };
- jest.spyOn(userApi, 'getCurrentUser').mockResolvedValue({ data: mockUser });
+ jest.spyOn(api, 'getCurrentUser').mockResolvedValue({ data: mockUser });
await testAction(
loadCurrentUser,
@@ -89,7 +87,7 @@ describe('JiraConnect actions', () => {
[],
);
- expect(userApi.getCurrentUser).toHaveBeenCalledWith({
+ expect(api.getCurrentUser).toHaveBeenCalledWith({
headers: { Authorization: `Bearer ${mockAccessToken}` },
});
});
@@ -97,7 +95,7 @@ describe('JiraConnect actions', () => {
describe('when API request fails', () => {
it('commits the SET_CURRENT_USER_ERROR mutation', async () => {
- jest.spyOn(userApi, 'getCurrentUser').mockRejectedValue();
+ jest.spyOn(api, 'getCurrentUser').mockRejectedValue();
await testAction(
loadCurrentUser,
@@ -120,9 +118,7 @@ describe('JiraConnect actions', () => {
describe('when API request succeeds', () => {
it('commits the SET_ACCESS_TOKEN and SET_CURRENT_USER mutations', async () => {
- jest
- .spyOn(integrationsApi, 'addJiraConnectSubscription')
- .mockResolvedValue({ success: true });
+ jest.spyOn(api, 'addJiraConnectSubscription').mockResolvedValue({ success: true });
await testAction(
addSubscription,
@@ -144,7 +140,7 @@ describe('JiraConnect actions', () => {
[{ type: 'fetchSubscriptions', payload: mockSubscriptionsPath }],
);
- expect(integrationsApi.addJiraConnectSubscription).toHaveBeenCalledWith(mockNamespace, {
+ expect(api.addJiraConnectSubscription).toHaveBeenCalledWith(mockNamespace, {
accessToken: null,
jwt: '1234',
});
@@ -153,7 +149,7 @@ describe('JiraConnect actions', () => {
describe('when API request fails', () => {
it('commits the SET_CURRENT_USER_ERROR mutation', async () => {
- jest.spyOn(integrationsApi, 'addJiraConnectSubscription').mockRejectedValue();
+ jest.spyOn(api, 'addJiraConnectSubscription').mockRejectedValue();
await testAction(
addSubscription,
diff --git a/spec/frontend/jira_import/components/jira_import_app_spec.js b/spec/frontend/jira_import/components/jira_import_app_spec.js
index cd8024d4962..022a0f81aaa 100644
--- a/spec/frontend/jira_import/components/jira_import_app_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_app_spec.js
@@ -21,15 +21,15 @@ describe('JiraImportApp', () => {
const setupIllustration = 'setup-illustration.svg';
- const getFormComponent = () => wrapper.find(JiraImportForm);
+ const getFormComponent = () => wrapper.findComponent(JiraImportForm);
- const getProgressComponent = () => wrapper.find(JiraImportProgress);
+ const getProgressComponent = () => wrapper.findComponent(JiraImportProgress);
- const getSetupComponent = () => wrapper.find(JiraImportSetup);
+ const getSetupComponent = () => wrapper.findComponent(JiraImportSetup);
- const getAlert = () => wrapper.find(GlAlert);
+ const getAlert = () => wrapper.findComponent(GlAlert);
- const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const mountComponent = ({
isJiraConfigured = true,
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 41d3cd46d01..d43a9f8a145 100644
--- a/spec/frontend/jira_import/components/jira_import_form_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_form_spec.js
@@ -164,8 +164,9 @@ describe('JiraImportForm', () => {
it('shows a heading for the user mapping section', () => {
expect(
- getByRole(wrapper.element, 'heading', { name: 'Jira-GitLab user mapping template' }),
- ).toBeTruthy();
+ getByRole(wrapper.element, 'heading', { name: 'Jira-GitLab user mapping template' })
+ .innerText,
+ ).toBe('Jira-GitLab user mapping template');
});
it('shows information to the user', () => {
@@ -182,15 +183,15 @@ describe('JiraImportForm', () => {
});
it('has a "Jira display name" column', () => {
- expect(getHeader('Jira display name')).toBeTruthy();
+ expect(getHeader('Jira display name').innerText).toBe('Jira display name');
});
it('has an "arrow" column', () => {
- expect(getHeader('Arrow')).toBeTruthy();
+ expect(getHeader('Arrow').getAttribute('aria-label')).toBe('Arrow');
});
it('has a "GitLab username" column', () => {
- expect(getHeader('GitLab username')).toBeTruthy();
+ expect(getHeader('GitLab username').innerText).toBe('GitLab username');
});
});
@@ -288,8 +289,8 @@ describe('JiraImportForm', () => {
});
it('updates the user list', () => {
- expect(getUserDropdown().findAll(GlDropdownItem)).toHaveLength(1);
- expect(getUserDropdown().find(GlDropdownItem).text()).toContain(
+ expect(getUserDropdown().findAllComponents(GlDropdownItem)).toHaveLength(1);
+ expect(getUserDropdown().findComponent(GlDropdownItem).text()).toContain(
'fchopin (Frederic Chopin)',
);
});
diff --git a/spec/frontend/jira_import/components/jira_import_progress_spec.js b/spec/frontend/jira_import/components/jira_import_progress_spec.js
index 04b2a2da622..42356763492 100644
--- a/spec/frontend/jira_import/components/jira_import_progress_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_progress_spec.js
@@ -8,7 +8,7 @@ describe('JiraImportProgress', () => {
const importProject = 'JIRAPROJECT';
- const getGlEmptyStateProp = (attribute) => wrapper.find(GlEmptyState).props(attribute);
+ const getGlEmptyStateProp = (attribute) => wrapper.findComponent(GlEmptyState).props(attribute);
const getParagraphText = () => wrapper.find('p').text();
diff --git a/spec/frontend/jira_import/components/jira_import_setup_spec.js b/spec/frontend/jira_import/components/jira_import_setup_spec.js
index 320e270b493..0085a2b5572 100644
--- a/spec/frontend/jira_import/components/jira_import_setup_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_setup_spec.js
@@ -6,7 +6,7 @@ import { illustration, jiraIntegrationPath } from '../mock_data';
describe('JiraImportSetup', () => {
let wrapper;
- const getGlEmptyStateProp = (attribute) => wrapper.find(GlEmptyState).props(attribute);
+ const getGlEmptyStateProp = (attribute) => wrapper.findComponent(GlEmptyState).props(attribute);
beforeEach(() => {
wrapper = shallowMount(JiraImportSetup, {
diff --git a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
index 322cfa3ba1f..98bdfc3fcbc 100644
--- a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
+++ b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
@@ -15,23 +15,27 @@ describe('Jobs filtered search', () => {
const findStatusToken = () => getSearchToken('status');
- const createComponent = () => {
- wrapper = shallowMount(JobsFilteredSearch);
+ const createComponent = (props) => {
+ wrapper = shallowMount(JobsFilteredSearch, {
+ propsData: {
+ ...props,
+ },
+ });
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
it('displays filtered search', () => {
+ createComponent();
+
expect(findFilteredSearch().exists()).toBe(true);
});
it('displays status token', () => {
+ createComponent();
+
expect(findStatusToken()).toMatchObject({
type: 'status',
icon: 'status',
@@ -42,8 +46,26 @@ describe('Jobs filtered search', () => {
});
it('emits filter token to parent component', () => {
+ createComponent();
+
findFilteredSearch().vm.$emit('submit', mockFailedSearchToken);
expect(wrapper.emitted('filterJobsBySearch')).toEqual([[mockFailedSearchToken]]);
});
+
+ it('filtered search value is empty array when no query string is passed', () => {
+ createComponent();
+
+ expect(findFilteredSearch().props('value')).toEqual([]);
+ });
+
+ it('filtered search returns correct data shape when passed query string', () => {
+ const value = 'SUCCESS';
+
+ createComponent({ queryString: { statuses: value } });
+
+ expect(findFilteredSearch().props('value')).toEqual([
+ { type: 'status', value: { data: value, operator: '=' } },
+ ]);
+ });
});
diff --git a/spec/frontend/jobs/components/filtered_search/utils_spec.js b/spec/frontend/jobs/components/filtered_search/utils_spec.js
new file mode 100644
index 00000000000..8440ab42b86
--- /dev/null
+++ b/spec/frontend/jobs/components/filtered_search/utils_spec.js
@@ -0,0 +1,19 @@
+import { validateQueryString } from '~/jobs/components/filtered_search/utils';
+
+describe('Filtered search utils', () => {
+ describe('validateQueryString', () => {
+ it.each`
+ queryStringObject | expected
+ ${{ statuses: 'SUCCESS' }} | ${{ statuses: 'SUCCESS' }}
+ ${{ statuses: 'failed' }} | ${{ statuses: 'FAILED' }}
+ ${{ wrong: 'SUCCESS' }} | ${null}
+ ${{ statuses: 'wrong' }} | ${null}
+ ${{ wrong: 'wrong' }} | ${null}
+ `(
+ 'when provided $queryStringObject, the expected result is $expected',
+ ({ queryStringObject, expected }) => {
+ expect(validateQueryString(queryStringObject)).toEqual(expected);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/jobs/components/artifacts_block_spec.js b/spec/frontend/jobs/components/job/artifacts_block_spec.js
index 0c7c0a6c311..c75deb64d84 100644
--- a/spec/frontend/jobs/components/artifacts_block_spec.js
+++ b/spec/frontend/jobs/components/job/artifacts_block_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import ArtifactsBlock from '~/jobs/components/artifacts_block.vue';
+import ArtifactsBlock from '~/jobs/components/job/sidebar/artifacts_block.vue';
import { getTimeago } from '~/lib/utils/datetime_utility';
describe('Artifacts block', () => {
diff --git a/spec/frontend/jobs/components/commit_block_spec.js b/spec/frontend/jobs/components/job/commit_block_spec.js
index 8a6d48cecb8..4fcc754c82c 100644
--- a/spec/frontend/jobs/components/commit_block_spec.js
+++ b/spec/frontend/jobs/components/job/commit_block_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import CommitBlock from '~/jobs/components/commit_block.vue';
+import CommitBlock from '~/jobs/components/job/sidebar/commit_block.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('Commit block', () => {
diff --git a/spec/frontend/jobs/components/empty_state_spec.js b/spec/frontend/jobs/components/job/empty_state_spec.js
index 9738fd14275..299b607ad78 100644
--- a/spec/frontend/jobs/components/empty_state_spec.js
+++ b/spec/frontend/jobs/components/job/empty_state_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import EmptyState from '~/jobs/components/empty_state.vue';
+import EmptyState from '~/jobs/components/job/empty_state.vue';
describe('Empty State', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/environments_block_spec.js b/spec/frontend/jobs/components/job/environments_block_spec.js
index d90c9137a8f..134533e2af8 100644
--- a/spec/frontend/jobs/components/environments_block_spec.js
+++ b/spec/frontend/jobs/components/job/environments_block_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import EnvironmentsBlock from '~/jobs/components/environments_block.vue';
+import EnvironmentsBlock from '~/jobs/components/job/environments_block.vue';
const TEST_CLUSTER_NAME = 'test_cluster';
const TEST_CLUSTER_PATH = 'path/to/test_cluster';
@@ -46,7 +46,7 @@ describe('Environments block', () => {
});
};
- const findText = () => wrapper.find(EnvironmentsBlock).text();
+ const findText = () => wrapper.findComponent(EnvironmentsBlock).text();
const findJobDeploymentLink = () => wrapper.find('[data-testid="job-deployment-link"]');
const findEnvironmentLink = () => wrapper.find('[data-testid="job-environment-link"]');
const findClusterLink = () => wrapper.find('[data-testid="job-cluster-link"]');
diff --git a/spec/frontend/jobs/components/erased_block_spec.js b/spec/frontend/jobs/components/job/erased_block_spec.js
index 057df20ccc2..c6aba01fa53 100644
--- a/spec/frontend/jobs/components/erased_block_spec.js
+++ b/spec/frontend/jobs/components/job/erased_block_spec.js
@@ -1,6 +1,6 @@
import { GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import ErasedBlock from '~/jobs/components/erased_block.vue';
+import ErasedBlock from '~/jobs/components/job/erased_block.vue';
import { getTimeago } from '~/lib/utils/datetime_utility';
describe('Erased block', () => {
@@ -10,7 +10,7 @@ describe('Erased block', () => {
const timeago = getTimeago();
const formattedDate = timeago.format(erasedAt);
- const findLink = () => wrapper.find(GlLink);
+ const findLink = () => wrapper.findComponent(GlLink);
const createComponent = (props) => {
wrapper = mount(ErasedBlock, {
diff --git a/spec/frontend/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job/job_app_spec.js
index b4b5bc4669d..822528403cf 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job/job_app_spec.js
@@ -5,16 +5,16 @@ import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
import delayedJobFixture from 'test_fixtures/jobs/delayed.json';
import { TEST_HOST } from 'helpers/test_constants';
-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 EmptyState from '~/jobs/components/job/empty_state.vue';
+import EnvironmentsBlock from '~/jobs/components/job/environments_block.vue';
+import ErasedBlock from '~/jobs/components/job/erased_block.vue';
+import JobApp from '~/jobs/components/job/job_app.vue';
+import Sidebar from '~/jobs/components/job/sidebar/sidebar.vue';
+import StuckBlock from '~/jobs/components/job/stuck_block.vue';
+import UnmetPrerequisitesBlock from '~/jobs/components/job/unmet_prerequisites_block.vue';
import createStore from '~/jobs/store';
import axios from '~/lib/utils/axios_utils';
-import job from '../mock_data';
+import job from '../../mock_data';
describe('Job App', () => {
Vue.use(Vuex);
@@ -57,18 +57,18 @@ describe('Job App', () => {
await nextTick();
};
- const findLoadingComponent = () => wrapper.find(GlLoadingIcon);
- const findSidebar = () => wrapper.find(Sidebar);
+ const findLoadingComponent = () => wrapper.findComponent(GlLoadingIcon);
+ const findSidebar = () => wrapper.findComponent(Sidebar);
const findJobContent = () => wrapper.find('[data-testid="job-content"');
- const findStuckBlockComponent = () => wrapper.find(StuckBlock);
+ const findStuckBlockComponent = () => wrapper.findComponent(StuckBlock);
const findStuckBlockWithTags = () => wrapper.find('[data-testid="job-stuck-with-tags"');
const findStuckBlockNoActiveRunners = () =>
wrapper.find('[data-testid="job-stuck-no-active-runners"');
- const findFailedJobComponent = () => wrapper.find(UnmetPrerequisitesBlock);
- const findEnvironmentsBlockComponent = () => wrapper.find(EnvironmentsBlock);
- const findErasedBlock = () => wrapper.find(ErasedBlock);
+ const findFailedJobComponent = () => wrapper.findComponent(UnmetPrerequisitesBlock);
+ const findEnvironmentsBlockComponent = () => wrapper.findComponent(EnvironmentsBlock);
+ const findErasedBlock = () => wrapper.findComponent(ErasedBlock);
const findArchivedJob = () => wrapper.find('[data-testid="archived-job"]');
- const findEmptyState = () => wrapper.find(EmptyState);
+ const findEmptyState = () => wrapper.findComponent(EmptyState);
const findJobNewIssueLink = () => wrapper.find('[data-testid="job-new-issue"]');
const findJobEmptyStateTitle = () => wrapper.find('[data-testid="job-empty-state-title"]');
const findJobLogScrollTop = () => wrapper.find('[data-testid="job-controller-scroll-top"]');
diff --git a/spec/frontend/jobs/components/job_container_item_spec.js b/spec/frontend/jobs/components/job/job_container_item_spec.js
index eb2b0184e5f..05c38dd74b7 100644
--- a/spec/frontend/jobs/components/job_container_item_spec.js
+++ b/spec/frontend/jobs/components/job/job_container_item_spec.js
@@ -2,9 +2,9 @@ import { GlIcon, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import delayedJobFixture from 'test_fixtures/jobs/delayed.json';
-import JobContainerItem from '~/jobs/components/job_container_item.vue';
+import JobContainerItem from '~/jobs/components/job/sidebar/job_container_item.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import job from '../mock_data';
+import job from '../../mock_data';
describe('JobContainerItem', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/job_log_controllers_spec.js b/spec/frontend/jobs/components/job/job_log_controllers_spec.js
index aa85253a177..5e9a73b4387 100644
--- a/spec/frontend/jobs/components/job_log_controllers_spec.js
+++ b/spec/frontend/jobs/components/job/job_log_controllers_spec.js
@@ -1,10 +1,10 @@
import { GlSearchBoxByClick } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import JobLogControllers from '~/jobs/components/job_log_controllers.vue';
+import JobLogControllers from '~/jobs/components/job/job_log_controllers.vue';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { backoffMockImplementation } from 'helpers/backoff_helper';
import * as commonUtils from '~/lib/utils/common_utils';
-import { mockJobLog } from '../mock_data';
+import { mockJobLog } from '../../mock_data';
const mockToastShow = jest.fn();
diff --git a/spec/frontend/jobs/components/job_retry_forward_deployment_modal_spec.js b/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js
index 08973223c08..d60043f33f7 100644
--- a/spec/frontend/jobs/components/job_retry_forward_deployment_modal_spec.js
+++ b/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js
@@ -1,17 +1,17 @@
import { GlLink, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import JobRetryForwardDeploymentModal from '~/jobs/components/job_retry_forward_deployment_modal.vue';
+import JobRetryForwardDeploymentModal from '~/jobs/components/job/sidebar/job_retry_forward_deployment_modal.vue';
import { JOB_RETRY_FORWARD_DEPLOYMENT_MODAL } from '~/jobs/constants';
import createStore from '~/jobs/store';
-import job from '../mock_data';
+import job from '../../mock_data';
describe('Job Retry Forward Deployment Modal', () => {
let store;
let wrapper;
const retryOutdatedJobDocsUrl = 'url-to-docs';
- const findLink = () => wrapper.find(GlLink);
- const findModal = () => wrapper.find(GlModal);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findModal = () => wrapper.findComponent(GlModal);
const createWrapper = ({ props = {}, provide = {}, stubs = {} } = {}) => {
store = createStore();
diff --git a/spec/frontend/jobs/components/job_sidebar_details_container_spec.js b/spec/frontend/jobs/components/job/job_sidebar_details_container_spec.js
index 4046f0269dd..4da17ed8366 100644
--- a/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
+++ b/spec/frontend/jobs/components/job/job_sidebar_details_container_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import DetailRow from '~/jobs/components/sidebar_detail_row.vue';
-import SidebarJobDetailsContainer from '~/jobs/components/sidebar_job_details_container.vue';
+import DetailRow from '~/jobs/components/job/sidebar/sidebar_detail_row.vue';
+import SidebarJobDetailsContainer from '~/jobs/components/job/sidebar/sidebar_job_details_container.vue';
import createStore from '~/jobs/store';
-import job from '../mock_data';
+import job from '../../mock_data';
describe('Job Sidebar Details Container', () => {
let store;
@@ -11,7 +11,7 @@ describe('Job Sidebar Details Container', () => {
const findJobTimeout = () => wrapper.findByTestId('job-timeout');
const findJobTags = () => wrapper.findByTestId('job-tags');
- const findAllDetailsRow = () => wrapper.findAll(DetailRow);
+ const findAllDetailsRow = () => wrapper.findAllComponents(DetailRow);
const createWrapper = ({ props = {} } = {}) => {
store = createStore();
diff --git a/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js b/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js
index ad72b9be261..18d5f35bde4 100644
--- a/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
+++ b/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js
@@ -1,7 +1,7 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import JobsSidebarRetryButton from '~/jobs/components/job_sidebar_retry_button.vue';
+import JobsSidebarRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue';
import createStore from '~/jobs/store';
-import job from '../mock_data';
+import job from '../../mock_data';
describe('Job Sidebar Retry Button', () => {
let store;
diff --git a/spec/frontend/jobs/components/jobs_container_spec.js b/spec/frontend/jobs/components/job/jobs_container_spec.js
index 127570b8184..2fde4d3020b 100644
--- a/spec/frontend/jobs/components/jobs_container_spec.js
+++ b/spec/frontend/jobs/components/job/jobs_container_spec.js
@@ -1,7 +1,7 @@
import { GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import JobsContainer from '~/jobs/components/jobs_container.vue';
+import JobsContainer from '~/jobs/components/job/sidebar/jobs_container.vue';
describe('Jobs List block', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js b/spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js
new file mode 100644
index 00000000000..184562b2968
--- /dev/null
+++ b/spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js
@@ -0,0 +1,156 @@
+import { GlSprintf, GlLink } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import LegacyManualVariablesForm from '~/jobs/components/job/legacy_manual_variables_form.vue';
+
+Vue.use(Vuex);
+
+describe('Manual Variables Form', () => {
+ let wrapper;
+ let store;
+
+ const requiredProps = {
+ action: {
+ path: '/play',
+ method: 'post',
+ button_title: 'Trigger this manual action',
+ },
+ };
+
+ const createComponent = (props = {}) => {
+ store = new Vuex.Store({
+ actions: {
+ triggerManualJob: jest.fn(),
+ },
+ });
+
+ wrapper = extendedWrapper(
+ mount(LegacyManualVariablesForm, {
+ propsData: { ...requiredProps, ...props },
+ store,
+ stubs: {
+ GlSprintf,
+ },
+ }),
+ );
+ };
+
+ const findHelpText = () => wrapper.findComponent(GlSprintf);
+ const findHelpLink = () => wrapper.findComponent(GlLink);
+
+ const findTriggerBtn = () => wrapper.findByTestId('trigger-manual-job-btn');
+ const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn');
+ const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn');
+ const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder');
+ const findCiVariableKey = () => wrapper.findByTestId('ci-variable-key');
+ const findAllCiVariableKeys = () => wrapper.findAllByTestId('ci-variable-key');
+ const findCiVariableValue = () => wrapper.findByTestId('ci-variable-value');
+ const findAllVariables = () => wrapper.findAllByTestId('ci-variable-row');
+
+ const setCiVariableKey = () => {
+ findCiVariableKey().setValue('new key');
+ findCiVariableKey().vm.$emit('change');
+ nextTick();
+ };
+
+ const setCiVariableKeyByPosition = (position, value) => {
+ findAllCiVariableKeys().at(position).setValue(value);
+ findAllCiVariableKeys().at(position).vm.$emit('change');
+ nextTick();
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('creates a new variable when user enters a new key value', async () => {
+ expect(findAllVariables()).toHaveLength(1);
+
+ await setCiVariableKey();
+
+ expect(findAllVariables()).toHaveLength(2);
+ });
+
+ it('does not create extra empty variables', async () => {
+ expect(findAllVariables()).toHaveLength(1);
+
+ await setCiVariableKey();
+
+ expect(findAllVariables()).toHaveLength(2);
+
+ await setCiVariableKey();
+
+ expect(findAllVariables()).toHaveLength(2);
+ });
+
+ it('removes the correct variable row', async () => {
+ const variableKeyNameOne = 'key-one';
+ const variableKeyNameThree = 'key-three';
+
+ await setCiVariableKeyByPosition(0, variableKeyNameOne);
+
+ await setCiVariableKeyByPosition(1, 'key-two');
+
+ await setCiVariableKeyByPosition(2, variableKeyNameThree);
+
+ expect(findAllVariables()).toHaveLength(4);
+
+ await findAllDeleteVarBtns().at(1).trigger('click');
+
+ expect(findAllVariables()).toHaveLength(3);
+
+ expect(findAllCiVariableKeys().at(0).element.value).toBe(variableKeyNameOne);
+ expect(findAllCiVariableKeys().at(1).element.value).toBe(variableKeyNameThree);
+ expect(findAllCiVariableKeys().at(2).element.value).toBe('');
+ });
+
+ it('trigger button is disabled after trigger action', async () => {
+ expect(findTriggerBtn().props('disabled')).toBe(false);
+
+ await findTriggerBtn().trigger('click');
+
+ expect(findTriggerBtn().props('disabled')).toBe(true);
+ });
+
+ it('delete variable button should only show when there is more than one variable', async () => {
+ expect(findDeleteVarBtn().exists()).toBe(false);
+
+ await setCiVariableKey();
+
+ expect(findDeleteVarBtn().exists()).toBe(true);
+ });
+
+ it('delete variable button placeholder should only exist when a user cannot remove', async () => {
+ expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
+ });
+
+ it('renders help text with provided link', () => {
+ expect(findHelpText().exists()).toBe(true);
+ expect(findHelpLink().attributes('href')).toBe(
+ '/help/ci/variables/index#add-a-cicd-variable-to-a-project',
+ );
+ });
+
+ it('passes variables in correct format', async () => {
+ jest.spyOn(store, 'dispatch');
+
+ await setCiVariableKey();
+
+ await findCiVariableValue().setValue('new value');
+
+ await findTriggerBtn().trigger('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('triggerManualJob', [
+ {
+ key: 'new key',
+ secret_value: 'new value',
+ },
+ ]);
+ });
+});
diff --git a/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js b/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
new file mode 100644
index 00000000000..cb32ca9d3dc
--- /dev/null
+++ b/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
@@ -0,0 +1,91 @@
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import JobRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue';
+import LegacySidebarHeader from '~/jobs/components/job/sidebar/legacy_sidebar_header.vue';
+import createStore from '~/jobs/store';
+import job from '../../mock_data';
+
+describe('Legacy Sidebar Header', () => {
+ let store;
+ let wrapper;
+
+ const findCancelButton = () => wrapper.findByTestId('cancel-button');
+ const findRetryButton = () => wrapper.findComponent(JobRetryButton);
+ const findEraseLink = () => wrapper.findByTestId('job-log-erase-link');
+
+ const createWrapper = (props) => {
+ store = createStore();
+
+ wrapper = extendedWrapper(
+ shallowMount(LegacySidebarHeader, {
+ propsData: {
+ job,
+ ...props,
+ },
+ store,
+ }),
+ );
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when job log is erasable', () => {
+ const path = '/root/ci-project/-/jobs/1447/erase';
+
+ beforeEach(() => {
+ createWrapper({
+ erasePath: path,
+ });
+ });
+
+ it('renders erase job link', () => {
+ expect(findEraseLink().exists()).toBe(true);
+ });
+
+ it('erase job link has correct path', () => {
+ expect(findEraseLink().attributes('href')).toBe(path);
+ });
+ });
+
+ describe('when job log is not erasable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('does not render erase button', () => {
+ expect(findEraseLink().exists()).toBe(false);
+ });
+ });
+
+ describe('when the job is retryable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('should render the retry button', () => {
+ expect(findRetryButton().props('href')).toBe(job.retry_path);
+ });
+ });
+
+ describe('when there is no retry path', () => {
+ it('should not render a retry button', async () => {
+ const copy = { ...job, retry_path: null };
+ createWrapper({ job: copy });
+
+ expect(findRetryButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when the job is cancelable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('should render link to cancel job', () => {
+ expect(findCancelButton().props('icon')).toBe('cancel');
+ expect(findCancelButton().attributes('href')).toBe(job.cancel_path);
+ });
+ });
+});
diff --git a/spec/frontend/jobs/components/manual_variables_form_spec.js b/spec/frontend/jobs/components/job/manual_variables_form_spec.js
index 6faab3ddf31..5806f9f75f9 100644
--- a/spec/frontend/jobs/components/manual_variables_form_spec.js
+++ b/spec/frontend/jobs/components/job/manual_variables_form_spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import ManualVariablesForm from '~/jobs/components/manual_variables_form.vue';
+import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue';
Vue.use(Vuex);
diff --git a/spec/frontend/jobs/components/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js
index 8d2680608ab..5c9c011b4ab 100644
--- a/spec/frontend/jobs/components/sidebar_detail_row_spec.js
+++ b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js
@@ -1,6 +1,6 @@
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import SidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue';
+import SidebarDetailRow from '~/jobs/components/job/sidebar/sidebar_detail_row.vue';
describe('Sidebar detail row', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/job/sidebar_header_spec.js b/spec/frontend/jobs/components/job/sidebar_header_spec.js
new file mode 100644
index 00000000000..cb32ca9d3dc
--- /dev/null
+++ b/spec/frontend/jobs/components/job/sidebar_header_spec.js
@@ -0,0 +1,91 @@
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import JobRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue';
+import LegacySidebarHeader from '~/jobs/components/job/sidebar/legacy_sidebar_header.vue';
+import createStore from '~/jobs/store';
+import job from '../../mock_data';
+
+describe('Legacy Sidebar Header', () => {
+ let store;
+ let wrapper;
+
+ const findCancelButton = () => wrapper.findByTestId('cancel-button');
+ const findRetryButton = () => wrapper.findComponent(JobRetryButton);
+ const findEraseLink = () => wrapper.findByTestId('job-log-erase-link');
+
+ const createWrapper = (props) => {
+ store = createStore();
+
+ wrapper = extendedWrapper(
+ shallowMount(LegacySidebarHeader, {
+ propsData: {
+ job,
+ ...props,
+ },
+ store,
+ }),
+ );
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when job log is erasable', () => {
+ const path = '/root/ci-project/-/jobs/1447/erase';
+
+ beforeEach(() => {
+ createWrapper({
+ erasePath: path,
+ });
+ });
+
+ it('renders erase job link', () => {
+ expect(findEraseLink().exists()).toBe(true);
+ });
+
+ it('erase job link has correct path', () => {
+ expect(findEraseLink().attributes('href')).toBe(path);
+ });
+ });
+
+ describe('when job log is not erasable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('does not render erase button', () => {
+ expect(findEraseLink().exists()).toBe(false);
+ });
+ });
+
+ describe('when the job is retryable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('should render the retry button', () => {
+ expect(findRetryButton().props('href')).toBe(job.retry_path);
+ });
+ });
+
+ describe('when there is no retry path', () => {
+ it('should not render a retry button', async () => {
+ const copy = { ...job, retry_path: null };
+ createWrapper({ job: copy });
+
+ expect(findRetryButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when the job is cancelable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('should render link to cancel job', () => {
+ expect(findCancelButton().props('icon')).toBe('cancel');
+ expect(findCancelButton().attributes('href')).toBe(job.cancel_path);
+ });
+ });
+});
diff --git a/spec/frontend/jobs/components/sidebar_spec.js b/spec/frontend/jobs/components/job/sidebar_spec.js
index 39c71986ce4..dc1aa67489d 100644
--- a/spec/frontend/jobs/components/sidebar_spec.js
+++ b/spec/frontend/jobs/components/job/sidebar_spec.js
@@ -1,27 +1,23 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import ArtifactsBlock from '~/jobs/components/artifacts_block.vue';
-import JobRetryForwardDeploymentModal from '~/jobs/components/job_retry_forward_deployment_modal.vue';
-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 ArtifactsBlock from '~/jobs/components/job/sidebar/artifacts_block.vue';
+import JobRetryForwardDeploymentModal from '~/jobs/components/job/sidebar/job_retry_forward_deployment_modal.vue';
+import JobsContainer from '~/jobs/components/job/sidebar/jobs_container.vue';
+import Sidebar from '~/jobs/components/job/sidebar/sidebar.vue';
+import StagesDropdown from '~/jobs/components/job/sidebar/stages_dropdown.vue';
import createStore from '~/jobs/store';
-import job, { jobsInStage } from '../mock_data';
+import job, { jobsInStage } from '../../mock_data';
describe('Sidebar details block', () => {
let store;
let wrapper;
const forwardDeploymentFailure = 'forward_deployment_failure';
- const findModal = () => wrapper.find(JobRetryForwardDeploymentModal);
+ const findModal = () => wrapper.findComponent(JobRetryForwardDeploymentModal);
const findArtifactsBlock = () => wrapper.findComponent(ArtifactsBlock);
- const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findNewIssueButton = () => wrapper.findByTestId('job-new-issue');
- const findRetryButton = () => wrapper.find(JobRetryButton);
const findTerminalLink = () => wrapper.findByTestId('terminal-link');
- const findEraseLink = () => wrapper.findByTestId('job-log-erase-link');
const createWrapper = (props) => {
store = createStore();
@@ -43,44 +39,6 @@ describe('Sidebar details block', () => {
wrapper.destroy();
});
- describe('when job log is erasable', () => {
- const path = '/root/ci-project/-/jobs/1447/erase';
-
- beforeEach(() => {
- createWrapper({
- erasePath: path,
- });
- });
-
- it('renders erase job link', () => {
- expect(findEraseLink().exists()).toBe(true);
- });
-
- it('erase job link has correct path', () => {
- expect(findEraseLink().attributes('href')).toBe(path);
- });
- });
-
- describe('when job log is not erasable', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('does not render erase button', () => {
- expect(findEraseLink().exists()).toBe(false);
- });
- });
-
- describe('when there is no retry path retry', () => {
- it('should not render a retry button', async () => {
- createWrapper();
- const copy = { ...job, retry_path: null };
- await store.dispatch('receiveJobSuccess', copy);
-
- expect(findRetryButton().exists()).toBe(false);
- });
- });
-
describe('without terminal path', () => {
it('does not render terminal link', async () => {
createWrapper();
@@ -109,15 +67,6 @@ describe('Sidebar details block', () => {
expect(findNewIssueButton().attributes('href')).toBe(job.new_issue_path);
expect(findNewIssueButton().text()).toBe('New issue');
});
-
- it('should render the retry button', () => {
- expect(findRetryButton().props('href')).toBe(job.retry_path);
- });
-
- it('should render link to cancel job', () => {
- expect(findCancelButton().props('icon')).toBe('cancel');
- expect(findCancelButton().attributes('href')).toBe(job.cancel_path);
- });
});
describe('forward deployment failure', () => {
@@ -155,16 +104,6 @@ describe('Sidebar details block', () => {
it('should render the modal', () => {
expect(findModal().exists()).toBe(true);
});
-
- it('should provide the modal id to the button and modal', () => {
- expect(findRetryButton().props('modalId')).toBe(forwardDeploymentFailureModalId);
- expect(findModal().props('modalId')).toBe(forwardDeploymentFailureModalId);
- });
-
- it('should provide the retry path to the button and modal', () => {
- expect(findRetryButton().props('href')).toBe(job.retry_path);
- expect(findModal().props('href')).toBe(job.retry_path);
- });
});
});
@@ -176,7 +115,7 @@ describe('Sidebar details block', () => {
describe('with stages', () => {
it('renders value provided as selectedStage as selected', () => {
- expect(wrapper.find(StagesDropdown).props('selectedStage')).toBe('aStage');
+ expect(wrapper.findComponent(StagesDropdown).props('selectedStage')).toBe('aStage');
});
});
@@ -184,7 +123,7 @@ describe('Sidebar details block', () => {
beforeEach(() => store.dispatch('receiveJobSuccess', job));
it('does not render jobs container', () => {
- expect(wrapper.find(JobsContainer).exists()).toBe(false);
+ expect(wrapper.findComponent(JobsContainer).exists()).toBe(false);
});
});
@@ -195,7 +134,7 @@ describe('Sidebar details block', () => {
});
it('renders list of jobs', () => {
- expect(wrapper.find(JobsContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(JobsContainer).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jobs/components/stages_dropdown_spec.js b/spec/frontend/jobs/components/job/stages_dropdown_spec.js
index f638213ef0c..61dec585e82 100644
--- a/spec/frontend/jobs/components/stages_dropdown_spec.js
+++ b/spec/frontend/jobs/components/job/stages_dropdown_spec.js
@@ -2,7 +2,7 @@ import { GlDropdown, GlDropdownItem, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Mousetrap from 'mousetrap';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import StagesDropdown from '~/jobs/components/stages_dropdown.vue';
+import StagesDropdown from '~/jobs/components/job/sidebar/stages_dropdown.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import * as copyToClipboard from '~/behaviors/copy_to_clipboard';
import {
@@ -10,7 +10,7 @@ import {
mockPipelineWithoutMR,
mockPipelineWithAttachedMR,
mockPipelineDetached,
-} from '../mock_data';
+} from '../../mock_data';
describe('Stages Dropdown', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/stuck_block_spec.js b/spec/frontend/jobs/components/job/stuck_block_spec.js
index 1580ed45e46..8dc570cce27 100644
--- a/spec/frontend/jobs/components/stuck_block_spec.js
+++ b/spec/frontend/jobs/components/job/stuck_block_spec.js
@@ -1,6 +1,6 @@
import { GlBadge, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import StuckBlock from '~/jobs/components/stuck_block.vue';
+import StuckBlock from '~/jobs/components/job/stuck_block.vue';
describe('Stuck Block Job component', () => {
let wrapper;
@@ -26,8 +26,8 @@ describe('Stuck Block Job component', () => {
wrapper.find('[data-testid="job-stuck-no-active-runners"]');
const findStuckNoRunners = () => wrapper.find('[data-testid="job-stuck-no-runners"]');
const findStuckWithTags = () => wrapper.find('[data-testid="job-stuck-with-tags"]');
- const findRunnerPathLink = () => wrapper.find(GlLink);
- const findAllBadges = () => wrapper.findAll(GlBadge);
+ const findRunnerPathLink = () => wrapper.findComponent(GlLink);
+ const findAllBadges = () => wrapper.findAllComponents(GlBadge);
describe('with no runners for project', () => {
beforeEach(() => {
diff --git a/spec/frontend/jobs/components/trigger_block_spec.js b/spec/frontend/jobs/components/job/trigger_block_spec.js
index 78596612d23..a1de8fd143f 100644
--- a/spec/frontend/jobs/components/trigger_block_spec.js
+++ b/spec/frontend/jobs/components/job/trigger_block_spec.js
@@ -1,6 +1,6 @@
import { GlButton, GlTableLite } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import TriggerBlock from '~/jobs/components/trigger_block.vue';
+import TriggerBlock from '~/jobs/components/job/sidebar/trigger_block.vue';
describe('Trigger block', () => {
let wrapper;
diff --git a/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js b/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js
index aeb85694e60..fb7d389c4d6 100644
--- a/spec/frontend/jobs/components/unmet_prerequisites_block_spec.js
+++ b/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js
@@ -1,6 +1,6 @@
import { GlAlert, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import UnmetPrerequisitesBlock from '~/jobs/components/unmet_prerequisites_block.vue';
+import UnmetPrerequisitesBlock from '~/jobs/components/job/unmet_prerequisites_block.vue';
describe('Unmet Prerequisites Block Job component', () => {
let wrapper;
@@ -23,7 +23,7 @@ describe('Unmet Prerequisites Block Job component', () => {
});
it('renders an alert with the correct message', () => {
- const container = wrapper.find(GlAlert);
+ const container = wrapper.findComponent(GlAlert);
const alertMessage =
'This job failed because the necessary resources were not successfully created.';
@@ -32,7 +32,7 @@ describe('Unmet Prerequisites Block Job component', () => {
});
it('renders link to help page', () => {
- const helpLink = wrapper.find(GlLink);
+ const helpLink = wrapper.findComponent(GlLink);
expect(helpLink).not.toBeNull();
expect(helpLink.text()).toContain('More information');
diff --git a/spec/frontend/jobs/components/log/line_header_spec.js b/spec/frontend/jobs/components/log/line_header_spec.js
index bdc8ae0eef0..ec8e79bba13 100644
--- a/spec/frontend/jobs/components/log/line_header_spec.js
+++ b/spec/frontend/jobs/components/log/line_header_spec.js
@@ -39,7 +39,7 @@ describe('Job Log Header Line', () => {
});
it('renders the line number component', () => {
- expect(wrapper.find(LineNumber).exists()).toBe(true);
+ expect(wrapper.findComponent(LineNumber).exists()).toBe(true);
});
it('renders a span the provided text', () => {
@@ -90,7 +90,7 @@ describe('Job Log Header Line', () => {
});
it('renders the duration badge', () => {
- expect(wrapper.find(DurationBadge).exists()).toBe(true);
+ expect(wrapper.findComponent(DurationBadge).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jobs/components/log/line_spec.js b/spec/frontend/jobs/components/log/line_spec.js
index bf80d90e299..50ebd1610d2 100644
--- a/spec/frontend/jobs/components/log/line_spec.js
+++ b/spec/frontend/jobs/components/log/line_spec.js
@@ -42,7 +42,7 @@ describe('Job Log Line', () => {
});
it('renders the line number component', () => {
- expect(wrapper.find(LineNumber).exists()).toBe(true);
+ expect(wrapper.findComponent(LineNumber).exists()).toBe(true);
});
it('renders a span the provided text', () => {
diff --git a/spec/frontend/jobs/components/table/job_table_app_spec.js b/spec/frontend/jobs/components/table/job_table_app_spec.js
index 374768c3ee4..8c724a8030b 100644
--- a/spec/frontend/jobs/components/table/job_table_app_spec.js
+++ b/spec/frontend/jobs/components/table/job_table_app_spec.js
@@ -11,12 +11,14 @@ import VueApollo from 'vue-apollo';
import { s__ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
import createFlash from '~/flash';
import getJobsQuery from '~/jobs/components/table/graphql/queries/get_jobs.query.graphql';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import JobsTableApp from '~/jobs/components/table/jobs_table_app.vue';
import JobsTableTabs from '~/jobs/components/table/jobs_table_tabs.vue';
import JobsFilteredSearch from '~/jobs/components/filtered_search/jobs_filtered_search.vue';
+import * as urlUtils from '~/lib/utils/url_utility';
import {
mockJobsResponsePaginated,
mockJobsResponseEmpty,
@@ -230,5 +232,17 @@ describe('Job table app', () => {
expect(createFlash).toHaveBeenCalledWith(expectedWarning);
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
});
+
+ it('updates URL query string when filtering jobs by status', async () => {
+ createComponent();
+
+ jest.spyOn(urlUtils, 'updateHistory');
+
+ await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
+
+ expect(urlUtils.updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/?statuses=FAILED`,
+ });
+ });
});
});
diff --git a/spec/frontend/jobs/store/actions_spec.js b/spec/frontend/jobs/store/actions_spec.js
index b9f97a3c3ae..0d11c4d56bf 100644
--- a/spec/frontend/jobs/store/actions_spec.js
+++ b/spec/frontend/jobs/store/actions_spec.js
@@ -111,7 +111,7 @@ describe('Job State actions', () => {
});
describe('success', () => {
- it('dispatches requestJob and receiveJobSuccess ', () => {
+ it('dispatches requestJob and receiveJobSuccess', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 121212, name: 'karma' });
return testAction(
@@ -137,7 +137,7 @@ describe('Job State actions', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
- it('dispatches requestJob and receiveJobError ', () => {
+ it('dispatches requestJob and receiveJobError', () => {
return testAction(
fetchJob,
null,
@@ -291,7 +291,7 @@ describe('Job State actions', () => {
mock.onGet(`${TEST_HOST}/endpoint/trace.json`).reply(500);
});
- it('dispatches requestJobLog and receiveJobLogError ', () => {
+ it('dispatches requestJobLog and receiveJobLogError', () => {
return testAction(
fetchJobLog,
null,
@@ -355,7 +355,7 @@ describe('Job State actions', () => {
window.clearTimeout = origTimeout;
});
- it('should commit STOP_POLLING_JOB_LOG mutation ', async () => {
+ it('should commit STOP_POLLING_JOB_LOG mutation', async () => {
const jobLogTimeout = 7;
await testAction(
@@ -370,7 +370,7 @@ describe('Job State actions', () => {
});
describe('receiveJobLogSuccess', () => {
- it('should commit RECEIVE_JOB_LOG_SUCCESS mutation ', () => {
+ it('should commit RECEIVE_JOB_LOG_SUCCESS mutation', () => {
return testAction(
receiveJobLogSuccess,
'hello world',
@@ -388,7 +388,7 @@ describe('Job State actions', () => {
});
describe('toggleCollapsibleLine', () => {
- it('should commit TOGGLE_COLLAPSIBLE_LINE mutation ', () => {
+ it('should commit TOGGLE_COLLAPSIBLE_LINE mutation', () => {
return testAction(
toggleCollapsibleLine,
{ isClosed: true },
@@ -400,7 +400,7 @@ describe('Job State actions', () => {
});
describe('requestJobsForStage', () => {
- it('should commit REQUEST_JOBS_FOR_STAGE mutation ', () => {
+ it('should commit REQUEST_JOBS_FOR_STAGE mutation', () => {
return testAction(
requestJobsForStage,
{ name: 'deploy' },
@@ -423,7 +423,7 @@ describe('Job State actions', () => {
});
describe('success', () => {
- it('dispatches requestJobsForStage and receiveJobsForStageSuccess ', () => {
+ it('dispatches requestJobsForStage and receiveJobsForStageSuccess', () => {
mock
.onGet(`${TEST_HOST}/jobs.json`)
.replyOnce(200, { latest_statuses: [{ id: 121212, name: 'build' }], retried: [] });
@@ -473,7 +473,7 @@ describe('Job State actions', () => {
});
describe('receiveJobsForStageSuccess', () => {
- it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation ', () => {
+ it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation', () => {
return testAction(
receiveJobsForStageSuccess,
[{ id: 121212, name: 'karma' }],
@@ -485,7 +485,7 @@ describe('Job State actions', () => {
});
describe('receiveJobsForStageError', () => {
- it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation ', () => {
+ it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation', () => {
return testAction(
receiveJobsForStageError,
null,
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index ea1ec383d6e..89cda3b0544 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -83,7 +83,7 @@ describe('Jobs Store Mutations', () => {
describe('with new job log', () => {
describe('log.lines', () => {
describe('when append is true', () => {
- it('sets the parsed log ', () => {
+ it('sets the parsed log', () => {
mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
append: true,
size: 511846,
@@ -107,7 +107,7 @@ describe('Jobs Store Mutations', () => {
});
describe('when it is defined', () => {
- it('sets the parsed log ', () => {
+ it('sets the parsed log', () => {
mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
append: false,
size: 511846,
diff --git a/spec/frontend/labels/components/delete_label_modal_spec.js b/spec/frontend/labels/components/delete_label_modal_spec.js
index 6204138f885..24a803d3f16 100644
--- a/spec/frontend/labels/components/delete_label_modal_spec.js
+++ b/spec/frontend/labels/components/delete_label_modal_spec.js
@@ -34,7 +34,7 @@ describe('~/labels/components/delete_label_modal', () => {
wrapper.destroy();
});
- const findModal = () => wrapper.find(GlModal);
+ const findModal = () => wrapper.findComponent(GlModal);
const findPrimaryModalButton = () => wrapper.findByTestId('delete-button');
describe('template', () => {
diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js
index 29b927ef628..5523cc0606e 100644
--- a/spec/frontend/lib/dompurify_spec.js
+++ b/spec/frontend/lib/dompurify_spec.js
@@ -203,7 +203,7 @@ describe('~/lib/dompurify', () => {
expect(el.getAttribute('rel')).toBe('noreferrer noopener');
});
- it('does not update `rel` values when target is not `_blank` ', () => {
+ it('does not update `rel` values when target is not `_blank`', () => {
const html = `<a href="https://example.com" target="_self" rel="help">internal</a>`;
const el = getSanitizedNode(html);
diff --git a/spec/frontend/lib/gfm/index_spec.js b/spec/frontend/lib/gfm/index_spec.js
index f53f809b799..7c383ae68a4 100644
--- a/spec/frontend/lib/gfm/index_spec.js
+++ b/spec/frontend/lib/gfm/index_spec.js
@@ -24,12 +24,6 @@ describe('gfm', () => {
};
describe('render', () => {
- it('processes Commonmark and provides an ast to the renderer function', async () => {
- const result = await markdownToAST('This is text');
-
- expect(result.type).toBe('root');
- });
-
it('transforms raw HTML into individual nodes in the AST', async () => {
const result = await markdownToAST('<strong>This is bold text</strong>');
@@ -46,216 +40,270 @@ describe('gfm', () => {
);
});
- it('returns the result of executing the renderer function', async () => {
- const rendered = { value: 'rendered tree' };
+ describe('with custom renderer', () => {
+ it('processes Commonmark and provides an ast to the renderer function', async () => {
+ const result = await markdownToAST('This is text');
- const result = await render({
- markdown: '<strong>This is bold text</strong>',
- renderer: () => {
- return rendered;
- },
+ expect(result.type).toBe('root');
});
- expect(result).toEqual(rendered);
+ it('returns the result of executing the renderer function', async () => {
+ const rendered = { value: 'rendered tree' };
+
+ const result = await render({
+ markdown: '<strong>This is bold text</strong>',
+ renderer: () => {
+ return rendered;
+ },
+ });
+
+ expect(result).toEqual(rendered);
+ });
});
- describe('when skipping the rendering of footnote reference and definition nodes', () => {
- it('transforms footnotes into footnotedefinition and footnotereference tags', async () => {
- const result = await markdownToAST(
- `footnote reference [^footnote]
+ describe('footnote references and footnote definitions', () => {
+ describe('when skipping the rendering of footnote reference and definition nodes', () => {
+ it('transforms footnotes into footnotedefinition and footnotereference tags', async () => {
+ const result = await markdownToAST(
+ `footnote reference [^footnote]
[^footnote]: Footnote definition`,
- ['footnoteReference', 'footnoteDefinition'],
- );
+ ['footnoteReference', 'footnoteDefinition'],
+ );
- expectInRoot(
- result,
- expect.objectContaining({
- children: expect.arrayContaining([
- expect.objectContaining({
- type: 'element',
- tagName: 'footnotereference',
- properties: {
- identifier: 'footnote',
- label: 'footnote',
- },
- }),
- ]),
- }),
- );
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ children: expect.arrayContaining([
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'footnotereference',
+ properties: {
+ identifier: 'footnote',
+ label: 'footnote',
+ },
+ }),
+ ]),
+ }),
+ );
- expectInRoot(
- result,
- expect.objectContaining({
- tagName: 'footnotedefinition',
- properties: {
- identifier: 'footnote',
- label: 'footnote',
- },
- }),
- );
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ tagName: 'footnotedefinition',
+ properties: {
+ identifier: 'footnote',
+ label: 'footnote',
+ },
+ }),
+ );
+ });
});
});
- describe('when skipping the rendering of code blocks', () => {
- it('transforms code nodes into codeblock html tags', async () => {
- const result = await markdownToAST(
- `
+ describe('code blocks', () => {
+ describe('when skipping the rendering of code blocks', () => {
+ it('transforms code nodes into codeblock html tags', async () => {
+ const result = await markdownToAST(
+ `
\`\`\`javascript
console.log('Hola');
\`\`\`\
`,
- ['code'],
- );
+ ['code'],
+ );
- expectInRoot(
- result,
- expect.objectContaining({
- tagName: 'codeblock',
- properties: {
- language: 'javascript',
- },
- }),
- );
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ tagName: 'codeblock',
+ properties: {
+ language: 'javascript',
+ },
+ }),
+ );
+ });
});
});
- describe('when skipping the rendering of reference definitions', () => {
- it('transforms code nodes into codeblock html tags', async () => {
- const result = await markdownToAST(
- `
+ describe('reference definitions', () => {
+ describe('when skipping the rendering of reference definitions', () => {
+ it('transforms code nodes into codeblock html tags', async () => {
+ const result = await markdownToAST(
+ `
[gitlab][gitlab]
[gitlab]: https://gitlab.com "GitLab"
`,
- ['definition'],
- );
+ ['definition'],
+ );
- expectInRoot(
- result,
- expect.objectContaining({
- type: 'element',
- tagName: 'referencedefinition',
- properties: {
- identifier: 'gitlab',
- title: 'GitLab',
- url: 'https://gitlab.com',
- },
- children: [
- {
- type: 'text',
- value: '[gitlab]: https://gitlab.com "GitLab"',
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'referencedefinition',
+ properties: {
+ identifier: 'gitlab',
+ title: 'GitLab',
+ url: 'https://gitlab.com',
},
- ],
- }),
- );
+ children: [
+ {
+ type: 'text',
+ value: '[gitlab]: https://gitlab.com "GitLab"',
+ },
+ ],
+ }),
+ );
+ });
});
});
- describe('when skipping the rendering of link and image references', () => {
- it('transforms linkReference and imageReference nodes into html tags', async () => {
- const result = await markdownToAST(
- `
+ describe('link and image references', () => {
+ describe('when skipping the rendering of link and image references', () => {
+ it('transforms linkReference and imageReference nodes into html tags', async () => {
+ const result = await markdownToAST(
+ `
[gitlab][gitlab] and ![GitLab Logo][gitlab-logo]
[gitlab]: https://gitlab.com "GitLab"
[gitlab-logo]: https://gitlab.com/gitlab-logo.png "GitLab Logo"
`,
- ['linkReference', 'imageReference'],
- );
+ ['linkReference', 'imageReference'],
+ );
- expectInRoot(
- result,
- expect.objectContaining({
- tagName: 'p',
- children: expect.arrayContaining([
- expect.objectContaining({
- type: 'element',
- tagName: 'a',
- properties: expect.objectContaining({
- href: 'https://gitlab.com',
- isReference: 'true',
- identifier: 'gitlab',
- title: 'GitLab',
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ tagName: 'p',
+ children: expect.arrayContaining([
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'a',
+ properties: expect.objectContaining({
+ href: 'https://gitlab.com',
+ isReference: 'true',
+ identifier: 'gitlab',
+ title: 'GitLab',
+ }),
}),
- }),
- expect.objectContaining({
- type: 'element',
- tagName: 'img',
- properties: expect.objectContaining({
- src: 'https://gitlab.com/gitlab-logo.png',
- isReference: 'true',
- identifier: 'gitlab-logo',
- title: 'GitLab Logo',
- alt: 'GitLab Logo',
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'img',
+ properties: expect.objectContaining({
+ src: 'https://gitlab.com/gitlab-logo.png',
+ isReference: 'true',
+ identifier: 'gitlab-logo',
+ title: 'GitLab Logo',
+ alt: 'GitLab Logo',
+ }),
}),
- }),
- ]),
- }),
- );
- });
+ ]),
+ }),
+ );
+ });
- it('normalizes the urls extracted from the reference definitions', async () => {
- const result = await markdownToAST(
- `
+ it('normalizes the urls extracted from the reference definitions', async () => {
+ const result = await markdownToAST(
+ `
[gitlab][gitlab] and ![GitLab Logo][gitlab]
[gitlab]: /url\\bar*baz
`,
- ['linkReference', 'imageReference'],
- );
+ ['linkReference', 'imageReference'],
+ );
+
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ tagName: 'p',
+ children: expect.arrayContaining([
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'a',
+ properties: expect.objectContaining({
+ href: '/url%5Cbar*baz',
+ }),
+ }),
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'img',
+ properties: expect.objectContaining({
+ src: '/url%5Cbar*baz',
+ }),
+ }),
+ ]),
+ }),
+ );
+ });
+ });
+ });
+
+ describe('frontmatter', () => {
+ describe('when skipping the rendering of frontmatter types', () => {
+ it.each`
+ type | input
+ ${'yaml'} | ${'---\ntitle: page\n---'}
+ ${'toml'} | ${'+++\ntitle: page\n+++'}
+ ${'json'} | ${';;;\ntitle: page\n;;;'}
+ `('transforms $type nodes into frontmatter html tags', async ({ input, type }) => {
+ const result = await markdownToAST(input, [type]);
+
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'frontmatter',
+ properties: {
+ language: type,
+ },
+ children: [
+ {
+ type: 'text',
+ value: 'title: page',
+ },
+ ],
+ }),
+ );
+ });
+ });
+ });
+
+ describe('table of contents', () => {
+ it.each`
+ markdown
+ ${'[[_TOC_]]'}
+ ${' [[_TOC_]]'}
+ ${'[[_TOC_]] '}
+ ${'[TOC]'}
+ ${' [TOC]'}
+ ${'[TOC] '}
+ `('parses $markdown and produces a table of contents section', async ({ markdown }) => {
+ const result = await markdownToAST(markdown);
expectInRoot(
result,
expect.objectContaining({
- tagName: 'p',
- children: expect.arrayContaining([
- expect.objectContaining({
- type: 'element',
- tagName: 'a',
- properties: expect.objectContaining({
- href: '/url%5Cbar*baz',
- }),
- }),
- expect.objectContaining({
- type: 'element',
- tagName: 'img',
- properties: expect.objectContaining({
- src: '/url%5Cbar*baz',
- }),
- }),
- ]),
+ type: 'element',
+ tagName: 'nav',
}),
);
});
});
- });
- describe('when skipping the rendering of frontmatter types', () => {
- it.each`
- type | input
- ${'yaml'} | ${'---\ntitle: page\n---'}
- ${'toml'} | ${'+++\ntitle: page\n+++'}
- ${'json'} | ${';;;\ntitle: page\n;;;'}
- `('transforms $type nodes into frontmatter html tags', async ({ input, type }) => {
- const result = await markdownToAST(input, [type]);
+ describe('when skipping the rendering of table of contents', () => {
+ it('transforms table of contents nodes into html tableofcontents tags', async () => {
+ const result = await markdownToAST('[[_TOC_]]', ['tableOfContents']);
- expectInRoot(
- result,
- expect.objectContaining({
- type: 'element',
- tagName: 'frontmatter',
- properties: {
- language: type,
- },
- children: [
- {
- type: 'text',
- value: 'title: page',
- },
- ],
- }),
- );
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'tableofcontents',
+ }),
+ );
+ });
});
});
});
diff --git a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js
index 06573f346e0..b972f669ac4 100644
--- a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js
+++ b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js
@@ -84,7 +84,7 @@ describe('StartupJSLink', () => {
});
});
- describe('variable match errors: ', () => {
+ describe('variable match errors:', () => {
it('forwards requests if the variables are not matching', () => {
window.gl = {
startup_graphql_calls: [
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index a2ace8857ed..a0140d1d8a8 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -476,7 +476,7 @@ describe('common_utils', () => {
});
});
- it('catches the rejected promise from the callback ', () => {
+ it('catches the rejected promise from the callback', () => {
const errorMessage = 'Mistakes were made!';
return commonUtils
.backOff((next, stop) => {
diff --git a/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js b/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js
index 47bb512cbb5..59b3b4c02df 100644
--- a/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js
@@ -1,4 +1,4 @@
-import { newDateAsLocaleTime } from '~/lib/utils/datetime/date_calculation_utility';
+import { getDateWithUTC, newDateAsLocaleTime } from '~/lib/utils/datetime/date_calculation_utility';
describe('newDateAsLocaleTime', () => {
it.each`
@@ -15,3 +15,19 @@ describe('newDateAsLocaleTime', () => {
expect(newDateAsLocaleTime(string)).toEqual(expected);
});
});
+
+describe('getDateWithUTC', () => {
+ it.each`
+ date | expected
+ ${new Date('2022-03-22T01:23:45.678Z')} | ${new Date('2022-03-22T00:00:00.000Z')}
+ ${new Date('1999-12-31T23:59:59.999Z')} | ${new Date('1999-12-31T00:00:00.000Z')}
+ ${2022} | ${null}
+ ${[]} | ${null}
+ ${{}} | ${null}
+ ${true} | ${null}
+ ${null} | ${null}
+ ${undefined} | ${null}
+ `('returns $expected given $string', ({ date, expected }) => {
+ expect(getDateWithUTC(date)).toEqual(expected);
+ });
+});
diff --git a/spec/frontend/lib/utils/finite_state_machine_spec.js b/spec/frontend/lib/utils/finite_state_machine_spec.js
index 441dd24c758..cfde3b8596e 100644
--- a/spec/frontend/lib/utils/finite_state_machine_spec.js
+++ b/spec/frontend/lib/utils/finite_state_machine_spec.js
@@ -50,13 +50,13 @@ describe('Finite State Machine', () => {
});
it('throws an error if the machine definition is invalid', () => {
- expect(() => machine(badDefinition)).toThrowError(
+ expect(() => machine(badDefinition)).toThrow(
'A state machine must have an initial state (`.initial`) and a dictionary of possible states (`.states`)',
);
});
it('throws an error if the initial state is invalid', () => {
- expect(() => machine(unstartableDefinition)).toThrowError(
+ expect(() => machine(unstartableDefinition)).toThrow(
`Cannot initialize the state machine to state '${STATE_IMPOSSIBLE}'. Is that one of the machine's defined states?`,
);
});
diff --git a/spec/frontend/lib/utils/is_navigating_away_spec.js b/spec/frontend/lib/utils/is_navigating_away_spec.js
index e1230fe96bf..b8a01a1706c 100644
--- a/spec/frontend/lib/utils/is_navigating_away_spec.js
+++ b/spec/frontend/lib/utils/is_navigating_away_spec.js
@@ -6,7 +6,7 @@ describe('isNavigatingAway', () => {
setNavigatingForTestsOnly(false);
});
- it.each([false, true])('it returns the navigation flag with value %s', (flag) => {
+ it.each([false, true])('returns the navigation flag with value %s', (flag) => {
setNavigatingForTestsOnly(flag);
expect(isNavigatingAway()).toEqual(flag);
});
diff --git a/spec/frontend/lib/utils/navigation_utility_spec.js b/spec/frontend/lib/utils/navigation_utility_spec.js
index 6d3a871eb33..4dbd50223d5 100644
--- a/spec/frontend/lib/utils/navigation_utility_spec.js
+++ b/spec/frontend/lib/utils/navigation_utility_spec.js
@@ -63,7 +63,7 @@ describe('initPrefetchLinks', () => {
expect(newLink.addEventListener).toHaveBeenCalled();
});
- it('it is not fired when less then 100ms over link', () => {
+ it('is not fired when less then 100ms over link', () => {
const mouseOverEvent = new Event('mouseover');
const mouseOutEvent = new Event('mouseout');
diff --git a/spec/frontend/lib/utils/poll_spec.js b/spec/frontend/lib/utils/poll_spec.js
index 1f150599983..94a5f5385b7 100644
--- a/spec/frontend/lib/utils/poll_spec.js
+++ b/spec/frontend/lib/utils/poll_spec.js
@@ -128,9 +128,11 @@ describe('Poll', () => {
errorCallback: callbacks.error,
});
+ expect(Polling.timeoutID).toBeNull();
+
Polling.makeDelayedRequest(1);
- expect(Polling.timeoutID).toBeTruthy();
+ expect(Polling.timeoutID).not.toBeNull();
return waitForAllCallsToFinish(2, () => {
Polling.stop();
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index 733d89fe08c..8d179baa505 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -586,6 +586,33 @@ describe('init markdown', () => {
);
});
+ it('only converts valid URLs', () => {
+ const notValidUrl = 'group::label';
+ const expectedUrlValue = 'url';
+ const expectedText = `other [${notValidUrl}](${expectedUrlValue}) text`;
+ const initialValue = `other ${notValidUrl} text`;
+
+ textArea.value = initialValue;
+ selectedIndex = initialValue.indexOf(notValidUrl);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + notValidUrl.length);
+
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected: notValidUrl,
+ wrap: false,
+ select,
+ });
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedUrlValue, 1));
+ expect(textArea.selectionEnd).toEqual(
+ expectedText.indexOf(expectedUrlValue, 1) + expectedUrlValue.length,
+ );
+ });
+
it('adds block tags on line above and below selection', () => {
selected = 'this text\nis multiple\nlines';
text = `before \n${selected}\nafter `;
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 8e31fc792c5..49a160c9f23 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -45,29 +45,18 @@ describe('text_utility', () => {
});
describe('slugify', () => {
- it('should remove accents and convert to lower case', () => {
- expect(textUtils.slugify('João')).toEqual('jo-o');
- });
- it('should replaces whitespaces with hyphens and convert to lower case', () => {
- expect(textUtils.slugify('My Input String')).toEqual('my-input-string');
- });
- it('should remove trailing whitespace and replace whitespaces within string with a hyphen', () => {
- expect(textUtils.slugify(' a new project ')).toEqual('a-new-project');
- });
- it('should only remove non-allowed special characters', () => {
- expect(textUtils.slugify('test!_pro-ject~')).toEqual('test-_pro-ject');
- });
- it('should squash multiple hypens', () => {
- expect(textUtils.slugify('test!!!!_pro-ject~')).toEqual('test-_pro-ject');
- });
- it('should return empty string if only non-allowed characters', () => {
- expect(textUtils.slugify('здрасти')).toEqual('');
- });
- it('should squash multiple separators', () => {
- expect(textUtils.slugify('Test:-)')).toEqual('test');
- });
- it('should trim any separators from the beginning and end of the slug', () => {
- expect(textUtils.slugify('-Test:-)-')).toEqual('test');
+ it.each`
+ title | input | output
+ ${'should remove accents and convert to lower case'} | ${'João'} | ${'jo-o'}
+ ${'should replaces whitespaces with hyphens and convert to lower case'} | ${'My Input String'} | ${'my-input-string'}
+ ${'should remove trailing whitespace and replace whitespaces within string with a hyphen'} | ${' a new project '} | ${'a-new-project'}
+ ${'should only remove non-allowed special characters'} | ${'test!_pro-ject~'} | ${'test-_pro-ject'}
+ ${'should squash to multiple non-allowed special characters'} | ${'test!!!!_pro-ject~'} | ${'test-_pro-ject'}
+ ${'should return empty string if only non-allowed characters'} | ${'дружба'} | ${''}
+ ${'should squash multiple separators'} | ${'Test:-)'} | ${'test'}
+ ${'should trim any separators from the beginning and end of the slug'} | ${'-Test:-)-'} | ${'test'}
+ `('$title', ({ input, output }) => {
+ expect(textUtils.slugify(input)).toBe(output);
});
});
diff --git a/spec/frontend/lib/utils/vuex_module_mappers_spec.js b/spec/frontend/lib/utils/vuex_module_mappers_spec.js
index 1821a15f677..d25a692dfea 100644
--- a/spec/frontend/lib/utils/vuex_module_mappers_spec.js
+++ b/spec/frontend/lib/utils/vuex_module_mappers_spec.js
@@ -128,7 +128,7 @@ describe('~/lib/utils/vuex_module_mappers', () => {
describe('with non-string object value', () => {
it('throws helpful error', () => {
- expect(() => mapVuexModuleActions((vm) => vm.bogus, { foo: () => {} })).toThrowError(
+ expect(() => mapVuexModuleActions((vm) => vm.bogus, { foo: () => {} })).toThrow(
REQUIRE_STRING_ERROR_MESSAGE,
);
});
diff --git a/spec/frontend/locale/sprintf_spec.js b/spec/frontend/locale/sprintf_spec.js
index 52e903b819f..e0d0e117ea4 100644
--- a/spec/frontend/locale/sprintf_spec.js
+++ b/spec/frontend/locale/sprintf_spec.js
@@ -63,12 +63,26 @@ describe('locale', () => {
it('does not escape parameters for escapeParameters = false', () => {
const input = 'contains %{safeContent}';
const parameters = {
- safeContent: '<strong>bold attempt</strong>',
+ safeContent: '15',
};
const output = sprintf(input, parameters, false);
- expect(output).toBe('contains <strong>bold attempt</strong>');
+ expect(output).toBe('contains 15');
+ });
+
+ describe('replaces duplicated % in input', () => {
+ it('removes duplicated percentage signs', () => {
+ const input = 'contains duplicated %{safeContent}%%';
+
+ const parameters = {
+ safeContent: '15',
+ };
+
+ const output = sprintf(input, parameters, false);
+
+ expect(output).toBe('contains duplicated 15%');
+ });
});
});
});
diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js
index 9b908e5b6f0..9172876e76f 100644
--- a/spec/frontend/members/components/avatars/user_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/user_avatar_spec.js
@@ -1,7 +1,7 @@
import { GlAvatarLink, GlBadge } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import UserAvatar from '~/members/components/avatars/user_avatar.vue';
-import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import { member as memberMock, member2faEnabled, orphanedMember } from '../../mock_data';
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index 06ccd107ce3..49c4c46c3ac 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -23,6 +23,7 @@ export const member = {
webUrl: 'https://gitlab.com/root',
avatarUrl: 'https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80&d=identicon',
blocked: false,
+ isBot: false,
twoFactorEnabled: false,
oncallSchedules: [{ name: 'schedule 1' }],
escalationPolicies: [{ name: 'policy 1' }],
diff --git a/spec/frontend/members/store/actions_spec.js b/spec/frontend/members/store/actions_spec.js
index d37e6871387..20dce639177 100644
--- a/spec/frontend/members/store/actions_spec.js
+++ b/spec/frontend/members/store/actions_spec.js
@@ -69,7 +69,7 @@ describe('Vuex members actions', () => {
payload: { error },
},
]),
- ).rejects.toThrowError(error);
+ ).rejects.toThrow(error);
});
});
});
@@ -122,7 +122,7 @@ describe('Vuex members actions', () => {
payload: { error },
},
]),
- ).rejects.toThrowError(error);
+ ).rejects.toThrow(error);
});
});
});
diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js
index b0c9459ff4f..0271483801c 100644
--- a/spec/frontend/members/utils_spec.js
+++ b/spec/frontend/members/utils_spec.js
@@ -1,5 +1,12 @@
import setWindowLocation from 'helpers/set_window_location_helper';
-import { DEFAULT_SORT, MEMBER_TYPES } from '~/members/constants';
+import {
+ DEFAULT_SORT,
+ MEMBER_TYPES,
+ I18N_USER_YOU,
+ I18N_USER_BLOCKED,
+ I18N_USER_BOT,
+ I188N_USER_2FA,
+} from '~/members/constants';
import {
generateBadges,
isGroup,
@@ -52,9 +59,10 @@ describe('Members Utils', () => {
it.each`
member | expected
- ${memberMock} | ${{ show: true, text: "It's you", variant: 'success' }}
- ${{ ...memberMock, user: { ...memberMock.user, blocked: true } }} | ${{ show: true, text: 'Blocked', variant: 'danger' }}
- ${member2faEnabled} | ${{ show: true, text: '2FA', variant: 'info' }}
+ ${memberMock} | ${{ show: true, text: I18N_USER_YOU, variant: 'success' }}
+ ${{ ...memberMock, user: { ...memberMock.user, blocked: true } }} | ${{ show: true, text: I18N_USER_BLOCKED, variant: 'danger' }}
+ ${{ ...memberMock, user: { ...memberMock.user, isBot: true } }} | ${{ show: true, text: I18N_USER_BOT, variant: 'muted' }}
+ ${member2faEnabled} | ${{ show: true, text: I188N_USER_2FA, variant: 'info' }}
`('returns expected output for "$expected.text" badge', ({ member, expected }) => {
expect(
generateBadges({ member, isCurrentUser: true, canManageMembers: true }),
diff --git a/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js b/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
index 4fdc4024e10..9b5641ef7b3 100644
--- a/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
+++ b/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
@@ -49,8 +49,8 @@ describe('Merge Conflict Resolver App', () => {
extendedWrapper(w).findByTestId('interactive-button');
const findFileInlineButton = (w = wrapper) => extendedWrapper(w).findByTestId('inline-button');
const findSideBySideButton = () => wrapper.findByTestId('side-by-side');
- const findInlineConflictLines = (w = wrapper) => w.find(InlineConflictLines);
- const findParallelConflictLines = (w = wrapper) => w.find(ParallelConflictLines);
+ const findInlineConflictLines = (w = wrapper) => w.findComponent(InlineConflictLines);
+ const findParallelConflictLines = (w = wrapper) => w.findComponent(ParallelConflictLines);
const findCommitMessageTextarea = () => wrapper.findByTestId('commit-message');
it('shows the amount of conflicts', () => {
diff --git a/spec/frontend/merge_conflicts/store/actions_spec.js b/spec/frontend/merge_conflicts/store/actions_spec.js
index 7cee6576b53..e73769cba51 100644
--- a/spec/frontend/merge_conflicts/store/actions_spec.js
+++ b/spec/frontend/merge_conflicts/store/actions_spec.js
@@ -48,7 +48,7 @@ describe('merge conflicts actions', () => {
);
});
- it('when data has type equal to error ', () => {
+ it('when data has type equal to error', () => {
mock.onGet(conflictsPath).reply(200, { type: 'error', message: 'error message' });
return testAction(
actions.fetchConflictsData,
@@ -63,7 +63,7 @@ describe('merge conflicts actions', () => {
);
});
- it('when request fails ', () => {
+ it('when request fails', () => {
mock.onGet(conflictsPath).reply(400);
return testAction(
actions.fetchConflictsData,
@@ -80,7 +80,7 @@ describe('merge conflicts actions', () => {
});
describe('setConflictsData', () => {
- it('INTERACTIVE_RESOLVE_MODE updates the correct file ', () => {
+ it('INTERACTIVE_RESOLVE_MODE updates the correct file', () => {
decorateFiles.mockReturnValue([{ bar: 'baz' }]);
return testAction(
actions.setConflictsData,
@@ -239,7 +239,7 @@ describe('merge conflicts actions', () => {
});
describe('setFileResolveMode', () => {
- it('INTERACTIVE_RESOLVE_MODE updates the correct file ', () => {
+ it('INTERACTIVE_RESOLVE_MODE updates the correct file', () => {
return testAction(
actions.setFileResolveMode,
{ file: files[0], mode: INTERACTIVE_RESOLVE_MODE },
@@ -257,7 +257,7 @@ describe('merge conflicts actions', () => {
);
});
- it('EDIT_RESOLVE_MODE updates the correct file ', async () => {
+ it('EDIT_RESOLVE_MODE updates the correct file', async () => {
restoreFileLinesState.mockReturnValue([]);
const file = {
...files[0],
@@ -286,7 +286,7 @@ describe('merge conflicts actions', () => {
});
describe('setPromptConfirmationState', () => {
- it('updates the correct file ', () => {
+ it('updates the correct file', () => {
return testAction(
actions.setPromptConfirmationState,
{ file: files[0], promptDiscardConfirmation: true },
@@ -315,7 +315,7 @@ describe('merge conflicts actions', () => {
],
};
- it('updates the correct file ', async () => {
+ it('updates the correct file', async () => {
const marLikeMockReturn = { foo: 'bar' };
markLine.mockReturnValue(marLikeMockReturn);
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 2001bb5f95e..c6e90a4b20d 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -333,7 +333,7 @@ describe('MergeRequestTabs', () => {
${'show'} | ${false} | ${'shows'}
${'diffs'} | ${true} | ${'hides'}
${'commits'} | ${true} | ${'hides'}
- `('it $hidesText expand button on $tab tab', ({ tab, hides }) => {
+ `('$hidesText expand button on $tab tab', ({ tab, hides }) => {
window.gon = { features: { movedMrSidebar: true } };
const expandButton = document.createElement('div');
diff --git a/spec/frontend/milestones/components/milestone_combobox_spec.js b/spec/frontend/milestones/components/milestone_combobox_spec.js
index a8e3d13dca0..ce5b2a1000b 100644
--- a/spec/frontend/milestones/components/milestone_combobox_spec.js
+++ b/spec/frontend/milestones/components/milestone_combobox_spec.js
@@ -96,19 +96,19 @@ describe('Milestone combobox component', () => {
const findNoResults = () => wrapper.find('[data-testid="milestone-combobox-no-results"]');
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findProjectMilestonesSection = () =>
wrapper.find('[data-testid="project-milestones-section"]');
const findProjectMilestonesDropdownItems = () =>
- findProjectMilestonesSection().findAll(GlDropdownItem);
+ findProjectMilestonesSection().findAllComponents(GlDropdownItem);
const findFirstProjectMilestonesDropdownItem = () => findProjectMilestonesDropdownItems().at(0);
const findGroupMilestonesSection = () => wrapper.find('[data-testid="group-milestones-section"]');
const findGroupMilestonesDropdownItems = () =>
- findGroupMilestonesSection().findAll(GlDropdownItem);
+ findGroupMilestonesSection().findAllComponents(GlDropdownItem);
const findFirstGroupMilestonesDropdownItem = () => findGroupMilestonesDropdownItems().at(0);
//
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 14f04d9b767..263d6225a9f 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -3,7 +3,8 @@
exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
- data-qa-selector="prometheus_graphs"
+ data-qa-selector="prometheus_graphs_content"
+ data-testid="prometheus-graphs"
environmentstate="available"
metricsdashboardbasepath="/monitoring/monitor-project/-/metrics?environment=1"
metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
@@ -60,6 +61,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
clearalltext="Clear all"
clearalltextclass="gl-px-5"
data-qa-selector="environments_dropdown"
+ data-testid="environments-dropdown"
headertext=""
hideheaderborder="true"
highlighteditemstitle="Selected"
diff --git a/spec/frontend/monitoring/components/charts/stacked_column_spec.js b/spec/frontend/monitoring/components/charts/stacked_column_spec.js
index 91fe36bc6e4..779ded090c2 100644
--- a/spec/frontend/monitoring/components/charts/stacked_column_spec.js
+++ b/spec/frontend/monitoring/components/charts/stacked_column_spec.js
@@ -72,7 +72,7 @@ describe('Stacked column chart component', () => {
]);
});
- it('chart options should configure data zoom and axis label ', () => {
+ it('chart options should configure data zoom and axis label', () => {
const chartOptions = findChart().props('option');
const xAxisType = findChart().props('xAxisType');
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index d797d9e2ad0..339c1710a9e 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -430,7 +430,7 @@ describe('Dashboard Panel', () => {
expect(findTimeChart().props().projectPath).toBe(mockProjectPath);
});
- it('it renders a time series chart with no errors', () => {
+ it('renders a time series chart with no errors', () => {
expect(wrapper.findComponent(MonitorTimeSeriesChart).exists()).toBe(true);
});
});
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 608404e5c5b..1de6b6e3e98 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -407,7 +407,7 @@ describe('Dashboard', () => {
await nextTick();
});
- it('it does not show loading icons in any group', async () => {
+ it('does not show loading icons in any group', async () => {
setupStoreWithData(store);
await nextTick();
@@ -614,11 +614,11 @@ describe('Dashboard', () => {
const findFirstDraggableRemoveButton = () =>
findDraggablePanels().at(0).find('.js-draggable-remove');
- it('it enables draggables', async () => {
+ it('enables draggables', async () => {
findRearrangeButton().vm.$emit('click');
await nextTick();
- expect(findRearrangeButton().attributes('pressed')).toBeTruthy();
+ expect(findRearrangeButton().attributes('pressed')).toBe('true');
expect(findEnabledDraggables().wrappers).toEqual(findDraggables().wrappers);
});
@@ -656,13 +656,13 @@ describe('Dashboard', () => {
expect(findDraggablePanels().length).toEqual(metricsDashboardPanelCount - 1);
});
- it('it disables draggables when clicked again', async () => {
+ it('disables draggables when clicked again', async () => {
findRearrangeButton().vm.$emit('click');
await nextTick();
findRearrangeButton().vm.$emit('click');
await nextTick();
- expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
+ expect(findRearrangeButton().attributes('pressed')).toBeUndefined();
expect(findEnabledDraggables().length).toBe(0);
});
});
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index 721992e710a..3ccaa2d28ac 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -163,9 +163,6 @@ describe('DashboardsDropdown', () => {
findItemAt(1).vm.$emit('click');
});
- it('emits a "selectDashboard" event', () => {
- expect(wrapper.emitted().selectDashboard).toBeTruthy();
- });
it('emits a "selectDashboard" event with dashboard information', () => {
expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[0]]);
});
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
index 755204dc721..b54ca926dae 100644
--- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
@@ -68,7 +68,7 @@ describe('DuplicateDashboardForm', () => {
await nextTick();
expect(findByRef('fileNameFormGroup').classes()).toContain('is-invalid');
- expect(findInvalidFeedback().text()).toBeTruthy();
+ expect(findInvalidFeedback().text()).toBe('The file name should have a .yml extension');
});
});
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
index 3032c236741..d83a9192876 100644
--- a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
@@ -72,7 +72,7 @@ describe('duplicate dashboard modal', () => {
await waitForPromises();
expect(okEvent.preventDefault).toHaveBeenCalled();
- expect(wrapper.emitted().dashboardDuplicated).toBeTruthy();
+ expect(wrapper.emitted('dashboardDuplicated')).toHaveLength(1);
expect(wrapper.emitted().dashboardDuplicated[0]).toEqual([dashboardGitResponse[0]]);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index d1a13fbf9cd..a872a7780eb 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -855,7 +855,7 @@ describe('Monitoring store actions', () => {
);
});
- it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => {
+ it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty', () => {
mockMutate.mockResolvedValue({
data: {
project: null,
diff --git a/spec/frontend/nav/components/top_nav_app_spec.js b/spec/frontend/nav/components/top_nav_app_spec.js
index 1d6ea99155b..745707c1d28 100644
--- a/spec/frontend/nav/components/top_nav_app_spec.js
+++ b/spec/frontend/nav/components/top_nav_app_spec.js
@@ -30,9 +30,10 @@ describe('~/nav/components/top_nav_app.vue', () => {
it('renders nav item dropdown', () => {
expect(findNavItemDropdown().attributes('href')).toBeUndefined();
expect(findNavItemDropdown().attributes()).toMatchObject({
- icon: 'hamburger',
- text: TEST_NAV_DATA.activeTitle,
+ icon: '',
+ text: '',
'no-flip': '',
+ 'no-caret': '',
});
});
diff --git a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
index 6cfbdb16111..048fca846ad 100644
--- a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
+++ b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
@@ -25,7 +25,7 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
};
const findMenuItems = () => wrapper.findAllComponents(TopNavMenuItem);
- const findMenuSections = () => wrapper.find(TopNavMenuSections);
+ const findMenuSections = () => wrapper.findComponent(TopNavMenuSections);
const findMenuSidebar = () => wrapper.find('[data-testid="menu-sidebar"]');
const findMenuSubview = () => wrapper.findComponent(KeepAliveSlots);
const hasFullWidthMenuSidebar = () => findMenuSidebar().classes('gl-w-full');
diff --git a/spec/frontend/nav/components/top_nav_menu_item_spec.js b/spec/frontend/nav/components/top_nav_menu_item_spec.js
index a7430d8c73f..b9cf39b8c1d 100644
--- a/spec/frontend/nav/components/top_nav_menu_item_spec.js
+++ b/spec/frontend/nav/components/top_nav_menu_item_spec.js
@@ -26,7 +26,7 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
});
};
- const findButton = () => wrapper.find(GlButton);
+ const findButton = () => wrapper.findComponent(GlButton);
const findButtonIcons = () =>
findButton()
.findAllComponents(GlIcon)
diff --git a/spec/frontend/nav/components/top_nav_menu_sections_spec.js b/spec/frontend/nav/components/top_nav_menu_sections_spec.js
index d56542fe572..0ed5cffd93f 100644
--- a/spec/frontend/nav/components/top_nav_menu_sections_spec.js
+++ b/spec/frontend/nav/components/top_nav_menu_sections_spec.js
@@ -4,11 +4,20 @@ import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue';
const TEST_SECTIONS = [
{
id: 'primary',
- menuItems: [{ id: 'test', href: '/test/href' }, { id: 'foo' }, { id: 'bar' }],
+ menuItems: [
+ { type: 'header', title: 'Heading' },
+ { type: 'item', id: 'test', href: '/test/href' },
+ { type: 'header', title: 'Another Heading' },
+ { type: 'item', id: 'foo' },
+ { type: 'item', id: 'bar' },
+ ],
},
{
id: 'secondary',
- menuItems: [{ id: 'lorem' }, { id: 'ipsum' }],
+ menuItems: [
+ { type: 'item', id: 'lorem' },
+ { type: 'item', id: 'ipsum' },
+ ],
},
];
@@ -25,10 +34,20 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
};
const findMenuItemModels = (parent) =>
- parent.findAll('[data-testid="menu-item"]').wrappers.map((x) => ({
- menuItem: x.props('menuItem'),
- classes: x.classes(),
- }));
+ parent.findAll('[data-testid="menu-header"],[data-testid="menu-item"]').wrappers.map((x) => {
+ return {
+ menuItem: x.vm
+ ? {
+ type: 'item',
+ ...x.props('menuItem'),
+ }
+ : {
+ type: 'header',
+ title: x.text(),
+ },
+ classes: x.classes(),
+ };
+ });
const findSectionModels = () =>
wrapper.findAll('[data-testid="menu-section"]').wrappers.map((x) => ({
classes: x.classes(),
@@ -45,32 +64,31 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
});
it('renders sections with menu items', () => {
+ const headerClasses = ['gl-px-4', 'gl-py-2', 'gl-text-gray-900', 'gl-display-block'];
+ const itemClasses = ['gl-w-full'];
+
expect(findSectionModels()).toEqual([
{
classes: [],
- menuItems: [
- {
- menuItem: TEST_SECTIONS[0].menuItems[0],
- classes: ['gl-w-full'],
- },
- ...TEST_SECTIONS[0].menuItems.slice(1).map((menuItem) => ({
+ menuItems: TEST_SECTIONS[0].menuItems.map((menuItem, index) => {
+ const classes = menuItem.type === 'header' ? [...headerClasses] : [...itemClasses];
+ if (index > 0) classes.push(menuItem.type === 'header' ? 'gl-pt-3!' : 'gl-mt-1');
+ return {
menuItem,
- classes: ['gl-w-full', 'gl-mt-1'],
- })),
- ],
+ classes,
+ };
+ }),
},
{
classes: [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-mt-3'],
- menuItems: [
- {
- menuItem: TEST_SECTIONS[1].menuItems[0],
- classes: ['gl-w-full'],
- },
- ...TEST_SECTIONS[1].menuItems.slice(1).map((menuItem) => ({
+ menuItems: TEST_SECTIONS[1].menuItems.map((menuItem, index) => {
+ const classes = menuItem.type === 'header' ? [...headerClasses] : [...itemClasses];
+ if (index > 0) classes.push(menuItem.type === 'header' ? 'gl-pt-3!' : 'gl-mt-1');
+ return {
menuItem,
- classes: ['gl-w-full', 'gl-mt-1'],
- })),
- ],
+ classes,
+ };
+ }),
},
]);
});
@@ -88,7 +106,7 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
menuItem.vm.$emit('click');
- expect(wrapper.emitted('menu-item-click')).toEqual([[TEST_SECTIONS[0].menuItems[1]]]);
+ expect(wrapper.emitted('menu-item-click')).toEqual([[TEST_SECTIONS[0].menuItems[3]]]);
});
});
diff --git a/spec/frontend/nav/mock_data.js b/spec/frontend/nav/mock_data.js
index c2ad86a4605..2052acfe001 100644
--- a/spec/frontend/nav/mock_data.js
+++ b/spec/frontend/nav/mock_data.js
@@ -1,7 +1,7 @@
import { range } from 'lodash';
export const TEST_NAV_DATA = {
- activeTitle: 'Test Active Title',
+ menuTitle: 'Test Menu Title',
primary: [
...['projects', 'groups'].map((view) => ({
id: view,
diff --git a/spec/frontend/notebook/cells/output/latex_spec.js b/spec/frontend/notebook/cells/output/latex_spec.js
index 848d2069421..ed3b63be50a 100644
--- a/spec/frontend/notebook/cells/output/latex_spec.js
+++ b/spec/frontend/notebook/cells/output/latex_spec.js
@@ -27,7 +27,7 @@ describe('LaTeX output cell', () => {
${1} | ${false}
`('sets `Prompt.show-output` to $expectation when index is $index', ({ index, expectation }) => {
const wrapper = createComponent(inlineLatex, index);
- const prompt = wrapper.find(Prompt);
+ const prompt = wrapper.findComponent(Prompt);
expect(prompt.props().count).toEqual(count);
expect(prompt.props().showOutput).toEqual(expectation);
diff --git a/spec/frontend/notebook/index_spec.js b/spec/frontend/notebook/index_spec.js
index 475c41a72f6..b79000a3505 100644
--- a/spec/frontend/notebook/index_spec.js
+++ b/spec/frontend/notebook/index_spec.js
@@ -11,7 +11,7 @@ describe('Notebook component', () => {
function buildComponent(notebook) {
return mount(Component, {
- propsData: { notebook, codeCssClass: 'js-code-class' },
+ propsData: { notebook },
provide: { relativeRawPath: '' },
}).vm;
}
@@ -46,10 +46,6 @@ describe('Notebook component', () => {
it('renders code cell', () => {
expect(vm.$el.querySelector('pre')).not.toBeNull();
});
-
- it('add code class to code blocks', () => {
- expect(vm.$el.querySelector('.js-code-class')).not.toBeNull();
- });
});
describe('with worksheets', () => {
@@ -72,9 +68,5 @@ describe('Notebook component', () => {
it('renders code cell', () => {
expect(vm.$el.querySelector('pre')).not.toBeNull();
});
-
- it('add code class to code blocks', () => {
- expect(vm.$el.querySelector('.js-code-class')).not.toBeNull();
- });
});
});
diff --git a/spec/frontend/notebook/lib/highlight_spec.js b/spec/frontend/notebook/lib/highlight_spec.js
deleted file mode 100644
index 944ccd6aa9f..00000000000
--- a/spec/frontend/notebook/lib/highlight_spec.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import Prism from '~/notebook/lib/highlight';
-
-describe('Highlight library', () => {
- it('imports python language', () => {
- expect(Prism.languages.python).toBeDefined();
- });
-
- it('uses custom CSS classes', () => {
- const el = document.createElement('div');
- el.innerHTML = Prism.highlight('console.log("a");', Prism.languages.javascript);
-
- expect(el.querySelector('.string')).not.toBeNull();
- expect(el.querySelector('.function')).not.toBeNull();
- });
-});
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 463787c148b..55e4ef42e37 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -586,10 +586,10 @@ describe('issue_comment_form component', () => {
${true}
${false}
`('when checkbox value is `$shouldCheckboxBeChecked`', ({ shouldCheckboxBeChecked }) => {
- it(`sets \`confidential\` to \`${shouldCheckboxBeChecked}\``, async () => {
+ it(`sets \`internal\` to \`${shouldCheckboxBeChecked}\``, async () => {
mountComponent({
mountFunction: mount,
- initialData: { note: 'confidential note' },
+ initialData: { note: 'internal note' },
noteableData: { ...notableDataMockCanUpdateIssuable },
});
@@ -606,7 +606,7 @@ describe('issue_comment_form component', () => {
findCommentButton().trigger('click');
const [providedData] = wrapper.vm.saveNote.mock.calls[0];
- expect(providedData.data.note.confidential).toBe(shouldCheckboxBeChecked);
+ expect(providedData.data.note.internal).toBe(shouldCheckboxBeChecked);
});
});
@@ -679,7 +679,7 @@ describe('issue_comment_form component', () => {
);
});
- it('clicking `add comment now`, should call note endpoint, set `isDraft` false ', () => {
+ it('clicking `add comment now`, should call note endpoint, set `isDraft` false', () => {
mountComponent({ mountFunction: mount, initialData: { note: 'a comment' } });
jest.spyOn(store, 'dispatch').mockResolvedValue();
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index a7e2f1efa09..f4ec7f835bb 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -1,5 +1,5 @@
-import { GlButton } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DiscussionCounter from '~/notes/components/discussion_counter.vue';
@@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => {
describe('has no discussions', () => {
it('does not render', () => {
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => {
it('does not render', () => {
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -75,7 +75,7 @@ describe('DiscussionCounter component', () => {
it('renders', () => {
updateStore();
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(true);
});
@@ -89,7 +89,7 @@ describe('DiscussionCounter component', () => {
({ blocksMerge, color }) => {
updateStore();
store.state.unresolvedDiscussionsCount = 1;
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge } });
expect(wrapper.find('[data-testid="discussions-counter-text"]').classes()).toContain(color);
},
@@ -97,60 +97,58 @@ describe('DiscussionCounter component', () => {
it.each`
title | resolved | groupLength
- ${'not allResolved'} | ${false} | ${4}
+ ${'not allResolved'} | ${false} | ${2}
${'allResolved'} | ${true} | ${1}
- `('renders correctly if $title', ({ resolved, groupLength }) => {
+ `('renders correctly if $title', async ({ resolved, groupLength }) => {
updateStore({ resolvable: true, resolved });
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ await wrapper.find('.dropdown-toggle').trigger('click');
- expect(wrapper.findAllComponents(GlButton)).toHaveLength(groupLength);
+ expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(groupLength);
});
});
describe('toggle all threads button', () => {
let toggleAllButton;
- const updateStoreWithExpanded = (expanded) => {
+ const updateStoreWithExpanded = async (expanded) => {
const discussion = { ...discussionMock, expanded };
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
- toggleAllButton = wrapper.find('.toggle-all-discussions-btn');
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ await wrapper.find('.dropdown-toggle').trigger('click');
+ toggleAllButton = wrapper.find('[data-testid="toggle-all-discussions-btn"]');
};
afterEach(() => wrapper.destroy());
- it('calls button handler when clicked', () => {
- updateStoreWithExpanded(true);
+ it('calls button handler when clicked', async () => {
+ await updateStoreWithExpanded(true);
- toggleAllButton.vm.$emit('click');
+ toggleAllButton.trigger('click');
expect(setExpandDiscussionsFn).toHaveBeenCalledTimes(1);
});
it('collapses all discussions if expanded', async () => {
- updateStoreWithExpanded(true);
+ await updateStoreWithExpanded(true);
expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('collapse');
- toggleAllButton.vm.$emit('click');
+ toggleAllButton.trigger('click');
await nextTick();
expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('expand');
});
it('expands all discussions if collapsed', async () => {
- updateStoreWithExpanded(false);
+ await updateStoreWithExpanded(false);
expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('expand');
- toggleAllButton.vm.$emit('click');
+ toggleAllButton.trigger('click');
await nextTick();
expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('collapse');
});
});
});
diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js
index 27206bddbfc..ed9fc47540d 100644
--- a/spec/frontend/notes/components/discussion_filter_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_spec.js
@@ -8,7 +8,14 @@ import createEventHub from '~/helpers/event_hub_factory';
import axios from '~/lib/utils/axios_utils';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
-import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import Tracking from '~/tracking';
+import {
+ DISCUSSION_FILTERS_DEFAULT_VALUE,
+ DISCUSSION_FILTER_TYPES,
+ ASC,
+ DESC,
+} from '~/notes/constants';
import notesModule from '~/notes/stores/modules';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@@ -28,6 +35,8 @@ describe('DiscussionFilter component', () => {
const findFilter = (filterType) =>
wrapper.find(`.dropdown-item[data-filter-type="${filterType}"]`);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
+
const mountComponent = () => {
const discussions = [
{
@@ -68,6 +77,7 @@ describe('DiscussionFilter component', () => {
mock.onGet(DISCUSSION_PATH).reply(200, '');
window.mrTabs = undefined;
wrapper = mountComponent();
+ jest.spyOn(Tracking, 'event');
});
afterEach(() => {
@@ -75,6 +85,65 @@ describe('DiscussionFilter component', () => {
mock.restore();
});
+ describe('default', () => {
+ beforeEach(() => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
+
+ it('has local storage sync with the correct props', () => {
+ expect(findLocalStorageSync().props('asString')).toBe(true);
+ });
+
+ it('calls setDiscussionSortDirection when update is emitted', () => {
+ findLocalStorageSync().vm.$emit('input', ASC);
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC });
+ });
+ });
+
+ describe('when asc', () => {
+ beforeEach(() => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
+
+ describe('when the dropdown is clicked', () => {
+ it('calls the right actions', () => {
+ wrapper.find('.js-newest-first').vm.$emit('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
+ direction: DESC,
+ });
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
+ property: DESC,
+ });
+ });
+ });
+ });
+
+ describe('when desc', () => {
+ beforeEach(() => {
+ store.state.discussionSortOrder = DESC;
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
+
+ describe('when the dropdown item is clicked', () => {
+ it('calls the right actions', () => {
+ wrapper.find('.js-oldest-first').vm.$emit('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
+ direction: ASC,
+ });
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
+ property: ASC,
+ });
+ });
+
+ it('sets is-checked to true on the active button in the dropdown', () => {
+ expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
+ });
+ });
+ });
+
it('renders the all filters', () => {
expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe(
discussionFiltersMock.length,
@@ -82,7 +151,7 @@ describe('DiscussionFilter component', () => {
});
it('renders the default selected item', () => {
- expect(wrapper.find('#discussion-filter-dropdown .dropdown-item').text().trim()).toBe(
+ expect(wrapper.find('.discussion-filter-container .dropdown-item').text().trim()).toBe(
discussionFiltersMock[0].title,
);
});
@@ -127,14 +196,6 @@ describe('DiscussionFilter component', () => {
expect(wrapper.vm.$store.state.commentsDisabled).toBe(false);
});
- it('renders a dropdown divider for the default filter', () => {
- const defaultFilter = wrapper.findAll(
- `.discussion-filter-container .dropdown-item-wrapper > *`,
- );
-
- expect(defaultFilter.at(1).classes('gl-new-dropdown-divider')).toBe(true);
- });
-
describe('Merge request tabs', () => {
eventHub = createEventHub();
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index 1b8b6bec490..a74d709ed3a 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -140,21 +140,21 @@ describe('DiscussionNotes', () => {
findNoteAtIndex(0).vm.$emit('handleDeleteNote');
await nextTick();
- expect(wrapper.emitted().deleteNote).toBeTruthy();
+ expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
it('emits startReplying when first note emits startReplying', async () => {
findNoteAtIndex(0).vm.$emit('startReplying');
await nextTick();
- expect(wrapper.emitted().startReplying).toBeTruthy();
+ expect(wrapper.emitted().startReplying).toHaveLength(1);
});
it('emits deleteNote when second note emits handleDeleteNote', async () => {
findNoteAtIndex(1).vm.$emit('handleDeleteNote');
await nextTick();
- expect(wrapper.emitted().deleteNote).toBeTruthy();
+ expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
});
@@ -169,7 +169,7 @@ describe('DiscussionNotes', () => {
note.vm.$emit('handleDeleteNote');
await nextTick();
- expect(wrapper.emitted().deleteNote).toBeTruthy();
+ expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
index 71406eeb7b4..a185f11ffaa 100644
--- a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
@@ -19,7 +19,7 @@ describe('ResolveWithIssueButton', () => {
wrapper.destroy();
});
- it('it should have a link with the provided link property as href', () => {
+ it('should have a link with the provided link property as href', () => {
const button = wrapper.findComponent(GlButton);
expect(button.attributes().href).toBe(url);
diff --git a/spec/frontend/notes/components/multiline_comment_form_spec.js b/spec/frontend/notes/components/multiline_comment_form_spec.js
index b027a261c15..8446bba340f 100644
--- a/spec/frontend/notes/components/multiline_comment_form_spec.js
+++ b/spec/frontend/notes/components/multiline_comment_form_spec.js
@@ -70,7 +70,7 @@ describe('MultilineCommentForm', () => {
glSelect.vm.$emit('change', { ...testLine });
expect(wrapper.vm.commentLineStart).toEqual(line);
- expect(wrapper.emitted('input')).toBeTruthy();
+ expect(wrapper.emitted('input')).toHaveLength(1);
// Once during created, once during updateCommentLineStart
expect(setSelectedCommentPosition).toHaveBeenCalledTimes(2);
});
diff --git a/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
new file mode 100644
index 00000000000..658e844a9b1
--- /dev/null
+++ b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
@@ -0,0 +1,35 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue';
+
+const emitData = {
+ noteId: '1',
+ addError: 'Error promoting the note to timeline event: %{error}',
+ addGenericError: 'Something went wrong while promoting the note to timeline event.',
+};
+
+describe('NoteTimelineEventButton', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(TimelineEventButton, {
+ propsData: {
+ noteId: '1',
+ isPromotionInProgress: true,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTimelineButton = () => wrapper.findComponent(GlButton);
+
+ it('emits click-promote-comment-to-event', async () => {
+ findTimelineButton().vm.$emit('click');
+
+ expect(wrapper.emitted('click-promote-comment-to-event')).toEqual([[emitData]]);
+ expect(findTimelineButton().props('disabled')).toEqual(true);
+ });
+});
diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js
index c2e56d3e7a7..3b5313744ff 100644
--- a/spec/frontend/notes/components/note_body_spec.js
+++ b/spec/frontend/notes/components/note_body_spec.js
@@ -74,11 +74,11 @@ describe('issue_note_body component', () => {
});
it.each`
- confidential | buttonText
- ${false} | ${'Save comment'}
- ${true} | ${'Save internal note'}
- `('renders save button with text "$buttonText"', ({ confidential, buttonText }) => {
- wrapper = createComponent({ props: { note: { ...note, confidential }, isEditing: true } });
+ internal | buttonText
+ ${false} | ${'Save comment'}
+ ${true} | ${'Save internal note'}
+ `('renders save button with text "$buttonText"', ({ internal, buttonText }) => {
+ wrapper = createComponent({ props: { note: { ...note, internal }, isEditing: true } });
expect(wrapper.findComponent(NoteForm).props('saveButtonTitle')).toBe(buttonText);
});
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index fad04e9063d..90473e7ccba 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -116,15 +116,15 @@ describe('issue_note_form component', () => {
});
it.each`
- confidential | placeholder
- ${false} | ${'Write a comment or drag your files here…'}
- ${true} | ${'Write an internal note or drag your files here…'}
+ internal | placeholder
+ ${false} | ${'Write a comment or drag your files here…'}
+ ${true} | ${'Write an internal note or drag your files here…'}
`(
- 'should set correct textarea placeholder text when discussion confidentiality is $confidential',
- ({ confidential, placeholder }) => {
+ 'should set correct textarea placeholder text when discussion confidentiality is $internal',
+ ({ internal, placeholder }) => {
props.note = {
...note,
- confidential,
+ internal,
};
wrapper = createComponentWrapper();
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 43fbc5e26dc..76177229cff 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NoteHeader from '~/notes/components/note_header.vue';
-import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
Vue.use(Vuex);
@@ -40,13 +40,19 @@ describe('NoteHeader component', () => {
availability: '',
};
- const createComponent = (props) => {
+ const createComponent = (props, userAttributes = false) => {
wrapper = shallowMountExtended(NoteHeader, {
store: new Vuex.Store({
actions,
}),
propsData: { ...props },
stubs: { GlSprintf, UserNameWithStatus },
+ provide: {
+ glFeatures: {
+ removeUserAttributesProjects: userAttributes,
+ removeUserAttributesGroups: userAttributes,
+ },
+ },
});
};
@@ -55,6 +61,26 @@ describe('NoteHeader component', () => {
wrapper = null;
});
+ describe('when removeUserAttributesProjects feature flag is enabled', () => {
+ it('does not render busy status', () => {
+ createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } }, true);
+
+ expect(wrapper.find('.note-header-info').text()).not.toContain('(Busy)');
+ });
+
+ it('does not render author status', () => {
+ createComponent({ author }, true);
+
+ expect(findAuthorStatus().exists()).toBe(false);
+ });
+
+ it('does not render username', () => {
+ createComponent({ author }, true);
+
+ expect(wrapper.find('.note-header-info').text()).not.toContain('@');
+ });
+ });
+
it('does not render discussion actions when includeToggle is false', () => {
createComponent({
includeToggle: false,
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index b34305688d9..2175849aeb9 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -97,7 +97,7 @@ describe('noteable_discussion component', () => {
`(
'reply button on form should have title "$saveButtonTitle" when note is $noteType',
async ({ isNoteInternal, saveButtonTitle }) => {
- wrapper.setProps({ discussion: { ...discussionMock, confidential: isNoteInternal } });
+ wrapper.setProps({ discussion: { ...discussionMock, internal: isNoteInternal } });
await nextTick();
const replyPlaceholder = wrapper.findComponent(ReplyPlaceholder);
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index e049c5bc0c8..b044d40cbe4 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -292,7 +292,7 @@ describe('issue_note', () => {
describe('internal note', () => {
it('has internal note class for internal notes', () => {
- createWrapper({ note: { ...note, confidential: true } });
+ createWrapper({ note: { ...note, internal: true } });
expect(wrapper.classes()).toContain('internal-note');
});
diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js
deleted file mode 100644
index 8b6e05da3c0..00000000000
--- a/spec/frontend/notes/components/sort_discussion_spec.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import SortDiscussion from '~/notes/components/sort_discussion.vue';
-import { ASC, DESC } from '~/notes/constants';
-import createStore from '~/notes/stores';
-import Tracking from '~/tracking';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-
-Vue.use(Vuex);
-
-describe('Sort Discussion component', () => {
- let wrapper;
- let store;
-
- const createComponent = () => {
- jest.spyOn(store, 'dispatch').mockImplementation();
-
- wrapper = shallowMount(SortDiscussion, {
- store,
- });
- };
-
- const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
-
- beforeEach(() => {
- store = createStore();
- jest.spyOn(Tracking, 'event');
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('default', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('has local storage sync with the correct props', () => {
- expect(findLocalStorageSync().props('asString')).toBe(true);
- });
-
- it('calls setDiscussionSortDirection when update is emitted', () => {
- findLocalStorageSync().vm.$emit('input', ASC);
-
- expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC });
- });
- });
-
- describe('when asc', () => {
- describe('when the dropdown is clicked', () => {
- it('calls the right actions', () => {
- createComponent();
-
- wrapper.find('.js-newest-first').vm.$emit('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
- direction: DESC,
- });
- expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
- property: DESC,
- });
- });
- });
-
- it('shows the "Oldest First" as the dropdown', () => {
- createComponent();
-
- expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Oldest first');
- });
- });
-
- describe('when desc', () => {
- beforeEach(() => {
- store.state.discussionSortOrder = DESC;
- createComponent();
- });
-
- describe('when the dropdown item is clicked', () => {
- it('calls the right actions', () => {
- wrapper.find('.js-oldest-first').vm.$emit('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
- direction: ASC,
- });
- expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
- property: ASC,
- });
- });
-
- it('sets is-checked to true on the active button in the dropdown', () => {
- expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
- });
- });
-
- it('shows the "Newest First" as the dropdown', () => {
- expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Newest first');
- });
- });
-});
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index 35b3dec6298..1b4e8026d84 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -110,16 +110,13 @@ describe('Discussion navigation mixin', () => {
});
describe.each`
- fn | args | currentId | expected
- ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'}
- ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'}
- ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'}
- ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'}
- ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'}
- ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'}
- ${'jumpToNextRelativeDiscussion'} | ${[null]} | ${null} | ${'a'}
- ${'jumpToNextRelativeDiscussion'} | ${['a']} | ${null} | ${'c'}
- ${'jumpToNextRelativeDiscussion'} | ${['e']} | ${'c'} | ${'a'}
+ fn | args | currentId | expected
+ ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'}
+ ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'}
+ ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'}
+ ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'}
+ ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'}
+ ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'}
`('$fn (args = $args, currentId = $currentId)', ({ fn, args, currentId, expected }) => {
beforeEach(() => {
store.state.notes.currentDiscussionId = currentId;
@@ -133,19 +130,12 @@ describe('Discussion navigation mixin', () => {
await nextTick();
});
- it('sets current discussion', () => {
- expect(store.state.notes.currentDiscussionId).toEqual(expected);
- });
-
it('expands discussion', () => {
expect(expandDiscussion).toHaveBeenCalled();
});
it('scrolls to element', () => {
- expect(utils.scrollToElement).toHaveBeenCalledWith(
- findDiscussion('div.discussion', expected),
- { behavior: 'auto' },
- );
+ expect(utils.scrollToElement).toHaveBeenCalled();
});
});
@@ -172,7 +162,7 @@ describe('Discussion navigation mixin', () => {
expect(utils.scrollToElementWithContext).toHaveBeenCalledWith(
findDiscussion('ul.notes', expected),
- { behavior: 'auto' },
+ { behavior: 'auto', offset: 0 },
);
});
});
@@ -213,7 +203,7 @@ describe('Discussion navigation mixin', () => {
it('scrolls to discussion', () => {
expect(utils.scrollToElement).toHaveBeenCalledWith(
findDiscussion('div.discussion', expected),
- { behavior: 'auto' },
+ { behavior: 'auto', offset: 0 },
);
});
});
@@ -244,7 +234,6 @@ describe('Discussion navigation mixin', () => {
it.each`
tabValue
${'diffs'}
- ${'show'}
${'other'}
`(
'calls scrollToFile with setHash as $hashValue when the tab is $tabValue',
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 02b27eca196..989dd74b6d0 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -4,6 +4,7 @@ import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
import createFlash from '~/flash';
+import toast from '~/vue_shared/plugins/global_toast';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import axios from '~/lib/utils/axios_utils';
import * as notesConstants from '~/notes/constants';
@@ -14,7 +15,9 @@ import mutations from '~/notes/stores/mutations';
import * as utils from '~/notes/stores/utils';
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 promoteTimelineEvent from '~/notes/graphql/promote_timeline_event.mutation.graphql';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
+import notesEventHub from '~/notes/event_hub';
import waitForPromises from 'helpers/wait_for_promises';
import { resetStore } from '../helpers';
import {
@@ -38,6 +41,8 @@ jest.mock('~/flash', () => {
return flash;
});
+jest.mock('~/vue_shared/plugins/global_toast');
+
describe('Actions Notes Store', () => {
let commit;
let dispatch;
@@ -1324,6 +1329,102 @@ describe('Actions Notes Store', () => {
});
});
+ describe('promoteCommentToTimelineEvent', () => {
+ const actionArgs = {
+ noteId: '1',
+ addError: 'addError: Create error',
+ addGenericError: 'addGenericError',
+ };
+ const commitSpy = jest.fn();
+
+ describe('for successful request', () => {
+ const timelineEventSuccessResponse = {
+ data: {
+ timelineEventPromoteFromNote: {
+ timelineEvent: {
+ id: 'gid://gitlab/IncidentManagement::TimelineEvent/19',
+ },
+ errors: [],
+ },
+ },
+ };
+
+ beforeEach(() => {
+ jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(timelineEventSuccessResponse);
+ });
+
+ it('calls gqClient mutation with the correct values', () => {
+ actions.promoteCommentToTimelineEvent({ commit: () => {} }, actionArgs);
+
+ expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
+ expect(utils.gqClient.mutate).toHaveBeenCalledWith({
+ mutation: promoteTimelineEvent,
+ variables: {
+ input: {
+ noteId: 'gid://gitlab/Note/1',
+ },
+ },
+ });
+ });
+
+ it('returns success response', () => {
+ jest.spyOn(notesEventHub, '$emit').mockImplementation(() => {});
+
+ return actions.promoteCommentToTimelineEvent({ commit: commitSpy }, actionArgs).then(() => {
+ expect(notesEventHub.$emit).toHaveBeenLastCalledWith(
+ 'comment-promoted-to-timeline-event',
+ );
+ expect(toast).toHaveBeenCalledWith('Comment added to the timeline.');
+ expect(commitSpy).toHaveBeenCalledWith(
+ mutationTypes.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS,
+ false,
+ );
+ });
+ });
+ });
+
+ describe('for failing request', () => {
+ const errorResponse = {
+ data: {
+ timelineEventPromoteFromNote: {
+ timelineEvent: null,
+ errors: ['Create error'],
+ },
+ },
+ };
+
+ it.each`
+ mockReject | message | captureError | error
+ ${true} | ${'addGenericError'} | ${true} | ${new Error()}
+ ${false} | ${'addError: Create error'} | ${false} | ${null}
+ `(
+ 'should show an error when submission fails',
+ ({ mockReject, message, captureError, error }) => {
+ const expectedAlertArgs = {
+ captureError,
+ error,
+ message,
+ };
+ if (mockReject) {
+ jest.spyOn(utils.gqClient, 'mutate').mockRejectedValueOnce(new Error());
+ } else {
+ jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(errorResponse);
+ }
+
+ return actions
+ .promoteCommentToTimelineEvent({ commit: commitSpy }, actionArgs)
+ .then(() => {
+ expect(createFlash).toHaveBeenCalledWith(expectedAlertArgs);
+ expect(commitSpy).toHaveBeenCalledWith(
+ mutationTypes.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS,
+ false,
+ );
+ });
+ },
+ );
+ });
+ });
+
describe('setFetchingState', () => {
it('commits SET_NOTES_FETCHING_STATE', () => {
return testAction(
diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js
index 6d078dcefcf..e03fa854e54 100644
--- a/spec/frontend/notes/stores/getters_spec.js
+++ b/spec/frontend/notes/stores/getters_spec.js
@@ -211,7 +211,7 @@ describe('Getters Notes Store', () => {
describe('isNotesFetched', () => {
it('should return the state for the fetching notes', () => {
- expect(getters.isNotesFetched(state)).toBeFalsy();
+ expect(getters.isNotesFetched(state)).toBe(false);
});
});
@@ -512,8 +512,8 @@ describe('Getters Notes Store', () => {
unresolvedDiscussionsIdsByDate: [],
};
- expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeFalsy();
- expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy();
+ expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeUndefined();
+ expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeUndefined();
});
});
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index e0a0fc43ffe..8809a496c52 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -74,7 +74,7 @@ describe('Notes Store mutations', () => {
});
describe('DELETE_NOTE', () => {
- it('should delete a note ', () => {
+ it('should delete a note', () => {
const state = { discussions: [discussionMock] };
const toDelete = discussionMock.notes[0];
const lengthBefore = discussionMock.notes.length;
diff --git a/spec/frontend/notifications/components/custom_notifications_modal_spec.js b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
index c5d201c3aec..cd04adac72d 100644
--- a/spec/frontend/notifications/components/custom_notifications_modal_spec.js
+++ b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
@@ -56,8 +56,8 @@ describe('CustomNotificationsModal', () => {
);
}
- const findModalBodyDescription = () => wrapper.find(GlSprintf);
- const findAllCheckboxes = () => wrapper.findAll(GlFormCheckbox);
+ const findModalBodyDescription = () => wrapper.findComponent(GlSprintf);
+ const findAllCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox);
const findCheckboxAt = (index) => findAllCheckboxes().at(index);
beforeEach(() => {
@@ -111,7 +111,7 @@ describe('CustomNotificationsModal', () => {
const checkbox = findCheckboxAt(index);
expect(checkbox.text()).toContain(eventName);
expect(checkbox.vm.$attrs.checked).toBe(enabled);
- expect(checkbox.find(GlLoadingIcon).exists()).toBe(loading);
+ expect(checkbox.findComponent(GlLoadingIcon).exists()).toBe(loading);
},
);
});
@@ -142,7 +142,7 @@ describe('CustomNotificationsModal', () => {
wrapper = createComponent({ injectedProperties });
- wrapper.find(GlModal).vm.$emit('show');
+ wrapper.findComponent(GlModal).vm.$emit('show');
await waitForPromises();
@@ -159,7 +159,7 @@ describe('CustomNotificationsModal', () => {
wrapper = createComponent();
- wrapper.find(GlModal).vm.$emit('show');
+ wrapper.findComponent(GlModal).vm.$emit('show');
expect(wrapper.vm.isLoading).toBe(true);
await waitForPromises();
@@ -176,7 +176,7 @@ describe('CustomNotificationsModal', () => {
mockAxios.onGet('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
wrapper = createComponent();
- wrapper.find(GlModal).vm.$emit('show');
+ wrapper.findComponent(GlModal).vm.$emit('show');
await waitForPromises();
@@ -197,7 +197,7 @@ describe('CustomNotificationsModal', () => {
${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 ',
+ 'updates the $notificationType notification settings when $condition and the user clicks the checkbox',
async ({ projectId, groupId, endpointUrl }) => {
mockAxios
.onGet(endpointUrl)
diff --git a/spec/frontend/notifications/components/notifications_dropdown_spec.js b/spec/frontend/notifications/components/notifications_dropdown_spec.js
index 7ca6c2052ae..7a98b374095 100644
--- a/spec/frontend/notifications/components/notifications_dropdown_spec.js
+++ b/spec/frontend/notifications/components/notifications_dropdown_spec.js
@@ -40,12 +40,13 @@ describe('NotificationsDropdown', () => {
});
}
- const findDropdown = () => wrapper.find(GlDropdown);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
- const findAllNotificationsDropdownItems = () => wrapper.findAll(NotificationsDropdownItem);
+ const findAllNotificationsDropdownItems = () =>
+ wrapper.findAllComponents(NotificationsDropdownItem);
const findDropdownItemAt = (index) =>
- findAllNotificationsDropdownItems().at(index).find(GlDropdownItem);
- const findNotificationsModal = () => wrapper.find(CustomNotificationsModal);
+ findAllNotificationsDropdownItems().at(index).findComponent(GlDropdownItem);
+ const findNotificationsModal = () => wrapper.findComponent(CustomNotificationsModal);
const clickDropdownItemAt = async (index) => {
const dropdownItem = findDropdownItemAt(index);
@@ -243,7 +244,7 @@ describe('NotificationsDropdown', () => {
expect(dropdownItem.props('isChecked')).toBe(true);
});
- it("won't update the selectedNotificationLevel and shows a toast message when the request fails and ", async () => {
+ 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();
diff --git a/spec/frontend/operation_settings/components/metrics_settings_spec.js b/spec/frontend/operation_settings/components/metrics_settings_spec.js
index 21145466016..810049220ae 100644
--- a/spec/frontend/operation_settings/components/metrics_settings_spec.js
+++ b/spec/frontend/operation_settings/components/metrics_settings_spec.js
@@ -63,7 +63,7 @@ describe('operation settings external dashboard component', () => {
describe('expand/collapse button', () => {
it('renders as an expand button by default', () => {
mountComponent();
- const button = wrapper.find(GlButton);
+ const button = wrapper.findComponent(GlButton);
expect(button.text()).toBe('Expand');
});
@@ -82,7 +82,7 @@ describe('operation settings external dashboard component', () => {
});
it('renders help page link', () => {
- const link = subHeader.find(GlLink);
+ const link = subHeader.findComponent(GlLink);
expect(link.text()).toBe('Learn more.');
expect(link.attributes().href).toBe(helpPage);
@@ -96,7 +96,7 @@ describe('operation settings external dashboard component', () => {
beforeEach(() => {
mountComponent(false);
- formGroup = wrapper.find(DashboardTimezone).find(GlFormGroup);
+ formGroup = wrapper.findComponent(DashboardTimezone).findComponent(GlFormGroup);
});
it('uses label text', () => {
@@ -117,7 +117,7 @@ describe('operation settings external dashboard component', () => {
beforeEach(() => {
mountComponent();
- select = wrapper.find(DashboardTimezone).find(GlFormSelect);
+ select = wrapper.findComponent(DashboardTimezone).findComponent(GlFormSelect);
});
it('defaults to externalDashboardUrl', () => {
@@ -132,7 +132,7 @@ describe('operation settings external dashboard component', () => {
beforeEach(() => {
mountComponent(false);
- formGroup = wrapper.find(ExternalDashboard).find(GlFormGroup);
+ formGroup = wrapper.findComponent(ExternalDashboard).findComponent(GlFormGroup);
});
it('uses label text', () => {
@@ -153,7 +153,7 @@ describe('operation settings external dashboard component', () => {
beforeEach(() => {
mountComponent();
- input = wrapper.find(ExternalDashboard).find(GlFormInput);
+ input = wrapper.findComponent(ExternalDashboard).findComponent(GlFormInput);
});
it('defaults to externalDashboardUrl', () => {
@@ -167,7 +167,7 @@ describe('operation settings external dashboard component', () => {
});
describe('submit button', () => {
- const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton);
+ const findSubmitButton = () => wrapper.find('.settings-content form').findComponent(GlButton);
const endpointRequest = [
operationsSettingsEndpoint,
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
index ad67128502a..ff11c8843bb 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
@@ -11,8 +11,8 @@ describe('delete_button', () => {
tooltipTitle: 'Bar tooltipTitle',
};
- const findButton = () => wrapper.find(GlButton);
- const findTooltip = () => wrapper.find(GlTooltip);
+ const findButton = () => wrapper.findComponent(GlButton);
+ const findTooltip = () => wrapper.findComponent(GlTooltip);
const mountComponent = (props) => {
wrapper = shallowMount(component, {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js
index 9680e273add..4a026f35822 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js
@@ -13,8 +13,8 @@ import {
describe('Delete alert', () => {
let wrapper;
- const findAlert = () => wrapper.find(GlAlert);
- const findLink = () => wrapper.find(GlLink);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLink = () => wrapper.findComponent(GlLink);
const mountComponent = (propsData) => {
wrapper = shallowMount(component, { stubs: { GlSprintf }, propsData });
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
index 9982286c625..b37edac83f7 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
@@ -120,7 +120,7 @@ describe('Details Header', () => {
return waitForPromises();
});
- it('shows image.name ', () => {
+ it('shows image.name', () => {
expect(findTitle().text()).toContain('foo');
});
@@ -289,7 +289,7 @@ describe('Details Header', () => {
);
});
- describe('visibility and updated at ', () => {
+ describe('visibility and updated at', () => {
it('has last updated text', async () => {
mountComponent();
await waitForMetadataItems();
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js
index 1a27481a828..ce5ecfe4608 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js
@@ -9,7 +9,7 @@ import {
describe('Partial Cleanup alert', () => {
let wrapper;
- const findAlert = () => wrapper.find(GlAlert);
+ const findAlert = () => wrapper.findComponent(GlAlert);
const findRunLink = () => wrapper.find('[data-testid="run-link"');
const findHelpLink = () => wrapper.find('[data-testid="help-link"');
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js
index a11b102d9a6..d83a5099bcd 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js
@@ -14,8 +14,8 @@ import {
describe('Status Alert', () => {
let wrapper;
- const findLink = () => wrapper.find(GlLink);
- const findAlert = () => wrapper.find(GlAlert);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findAlert = () => wrapper.findComponent(GlAlert);
const findMessage = () => wrapper.find('[data-testid="message"]');
const mountComponent = (propsData) => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
index 84f01f10f21..96c670eaad2 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
@@ -32,7 +32,7 @@ describe('tags list row', () => {
const findShortRevision = () => wrapper.find('[data-testid="digest"]');
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip);
- const findDetailsRows = () => wrapper.findAll(DetailsRow);
+ const findDetailsRows = () => wrapper.findAllComponents(DetailsRow);
const findPublishedDateDetail = () => wrapper.find('[data-testid="published-date-detail"]');
const findManifestDetail = () => wrapper.find('[data-testid="manifest-detail"]');
const findConfigurationDetail = () => wrapper.find('[data-testid="configuration-detail"]');
@@ -359,7 +359,7 @@ describe('tags list row', () => {
mountComponent();
await nextTick();
- expect(finderFunction().find(ClipboardButton).exists()).toBe(clipboard);
+ expect(finderFunction().findComponent(ClipboardButton).exists()).toBe(clipboard);
});
it('is disabled when the component is disabled', async () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js
index e5df260a260..88e79c513bc 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js
@@ -5,7 +5,7 @@ import { GlSkeletonLoader } from '../../stubs';
describe('TagsLoader component', () => {
let wrapper;
- const findGlSkeletonLoaders = () => wrapper.findAll(GlSkeletonLoader);
+ const findGlSkeletonLoaders = () => wrapper.findAllComponents(GlSkeletonLoader);
const mountComponent = () => {
wrapper = shallowMount(component, {
@@ -25,7 +25,7 @@ describe('TagsLoader component', () => {
wrapper = null;
});
- it('produces the correct amount of loaders ', () => {
+ it('produces the correct amount of loaders', () => {
mountComponent();
expect(findGlSkeletonLoaders().length).toBe(1);
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js
index 61503d0f3bf..535faebdd4e 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js
@@ -16,7 +16,7 @@ describe('cleanup_status', () => {
let wrapper;
const findMainIcon = () => wrapper.findByTestId('main-icon');
- const findMainIconName = () => wrapper.findByTestId('main-icon').find(GlIcon);
+ const findMainIconName = () => wrapper.findByTestId('main-icon').findComponent(GlIcon);
const findExtraInfoIcon = () => wrapper.findByTestId('extra-info');
const findPopover = () => wrapper.findComponent(GlPopover);
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
index d12933526bc..0b59fe2d8ce 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
@@ -233,7 +233,7 @@ describe('Image List Row', () => {
it('contains a tag icon', () => {
mountComponent();
- const icon = findTagsCount().find(GlIcon);
+ const icon = findTagsCount().findComponent(GlIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('tag');
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js
index e0119954ed4..042b8383571 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js
@@ -8,8 +8,8 @@ import { imagesListResponse, pageInfo as defaultPageInfo } from '../../mock_data
describe('Image List', () => {
let wrapper;
- const findRow = () => wrapper.findAll(ImageListRow);
- const findPagination = () => wrapper.find(GlKeysetPagination);
+ const findRow = () => wrapper.findAllComponents(ImageListRow);
+ const findPagination = () => wrapper.findComponent(GlKeysetPagination);
const mountComponent = (props) => {
wrapper = shallowMount(Component, {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
index a006de9f00c..e6d81d4a28f 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
@@ -17,7 +17,7 @@ jest.mock('~/lib/utils/datetime_utility', () => ({
describe('registry_header', () => {
let wrapper;
- const findTitleArea = () => wrapper.find(TitleArea);
+ const findTitleArea = () => wrapper.findComponent(TitleArea);
const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]');
const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]');
const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]');
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
index 1d161888a4d..ee6470a9df8 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
@@ -45,16 +45,16 @@ describe('Details Page', () => {
let wrapper;
let apolloProvider;
- const findDeleteModal = () => wrapper.find(DeleteModal);
- const findPagination = () => wrapper.find(GlKeysetPagination);
- const findTagsLoader = () => wrapper.find(TagsLoader);
- const findTagsList = () => wrapper.find(TagsList);
- const findDeleteAlert = () => wrapper.find(DeleteAlert);
- const findDetailsHeader = () => wrapper.find(DetailsHeader);
- const findEmptyState = () => wrapper.find(GlEmptyState);
- const findPartialCleanupAlert = () => wrapper.find(PartialCleanupAlert);
- const findStatusAlert = () => wrapper.find(StatusAlert);
- const findDeleteImage = () => wrapper.find(DeleteImage);
+ const findDeleteModal = () => wrapper.findComponent(DeleteModal);
+ const findPagination = () => wrapper.findComponent(GlKeysetPagination);
+ const findTagsLoader = () => wrapper.findComponent(TagsLoader);
+ const findTagsList = () => wrapper.findComponent(TagsList);
+ const findDeleteAlert = () => wrapper.findComponent(DeleteAlert);
+ const findDetailsHeader = () => wrapper.findComponent(DetailsHeader);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findPartialCleanupAlert = () => wrapper.findComponent(PartialCleanupAlert);
+ const findStatusAlert = () => wrapper.findComponent(StatusAlert);
+ const findDeleteImage = () => wrapper.findComponent(DeleteImage);
const routeId = 1;
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js
index 5f4cb8969bc..add772d27ef 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js
@@ -4,7 +4,7 @@ import component from '~/packages_and_registries/container_registry/explorer/pag
describe('List Page', () => {
let wrapper;
- const findRouterView = () => wrapper.find({ ref: 'router-view' });
+ const findRouterView = () => wrapper.findComponent({ ref: 'router-view' });
const mountComponent = () => {
wrapper = shallowMount(component, {
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js
new file mode 100644
index 00000000000..a2e5cbdce8b
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js
@@ -0,0 +1,143 @@
+import { GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { n__ } from '~/locale';
+import ArtifactsListRow from '~/packages_and_registries/harbor_registry/components/details/artifacts_list_row.vue';
+import RealListItem from '~/vue_shared/components/registry/list_item.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { harborArtifactsList, defaultConfig } from '../../mock_data';
+
+describe('Harbor artifact list row', () => {
+ let wrapper;
+
+ const ListItem = {
+ ...RealListItem,
+ data() {
+ return {
+ detailsSlots: [],
+ isDetailsShown: true,
+ };
+ },
+ };
+
+ const RouterLinkStub = {
+ props: {
+ to: {
+ type: Object,
+ },
+ },
+ render(createElement) {
+ return createElement('a', {}, this.$slots.default);
+ },
+ };
+
+ const findListItem = () => wrapper.findComponent(ListItem);
+ const findClipboardButton = () => wrapper.findAllComponents(ClipboardButton);
+ const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip);
+ const findByTestId = (testId) => wrapper.findByTestId(testId);
+
+ const $route = {
+ params: {
+ project: defaultConfig.harborIntegrationProjectName,
+ image: 'test-repository',
+ },
+ };
+
+ const mountComponent = ({ propsData, config = defaultConfig }) => {
+ wrapper = shallowMountExtended(ArtifactsListRow, {
+ stubs: {
+ GlSprintf,
+ ListItem,
+ 'router-link': RouterLinkStub,
+ },
+ mocks: {
+ $route,
+ },
+ propsData,
+ provide() {
+ return {
+ ...config,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('list item', () => {
+ beforeEach(() => {
+ mountComponent({
+ propsData: {
+ artifact: harborArtifactsList[0],
+ },
+ });
+ });
+
+ it('exists', () => {
+ expect(findListItem().exists()).toBe(true);
+ });
+
+ it('has the correct artifact name', () => {
+ expect(findByTestId('name').text()).toBe(harborArtifactsList[0].digest);
+ });
+
+ it('has the correct tags count', () => {
+ const tagsCount = harborArtifactsList[0].tags.length;
+ expect(findByTestId('tags-count').text()).toBe(n__('%d tag', '%d tags', tagsCount));
+ });
+
+ it('has correct digest', () => {
+ expect(findByTestId('digest').text()).toBe('Digest: mock_sh');
+ });
+ describe('time', () => {
+ it('has the correct push time', () => {
+ expect(findByTestId('time').text()).toBe('Published');
+ expect(findTimeAgoTooltip().attributes()).toMatchObject({
+ time: harborArtifactsList[0].pushTime,
+ });
+ });
+ });
+
+ describe('clipboard button', () => {
+ it('exists', () => {
+ expect(findClipboardButton()).toHaveLength(2);
+ });
+
+ it('has the correct props', () => {
+ expect(findClipboardButton().at(0).attributes()).toMatchObject({
+ text: `docker pull demo.harbor.com/test-project/test-repository@${harborArtifactsList[0].digest}`,
+ title: `docker pull demo.harbor.com/test-project/test-repository@${harborArtifactsList[0].digest}`,
+ });
+
+ expect(findClipboardButton().at(1).attributes()).toMatchObject({
+ text: harborArtifactsList[0].digest,
+ title: harborArtifactsList[0].digest,
+ });
+ });
+ });
+
+ describe('size', () => {
+ it('calculated correctly', () => {
+ expect(findByTestId('size').text()).toBe(
+ numberToHumanSize(Number(harborArtifactsList[0].size)),
+ );
+ });
+
+ it('when size is missing', () => {
+ const artifactInfo = harborArtifactsList[0];
+ artifactInfo.size = null;
+
+ mountComponent({
+ propsData: {
+ artifact: artifactInfo,
+ },
+ });
+
+ expect(findByTestId('size').text()).toBe('0 bytes');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js
new file mode 100644
index 00000000000..b9d6dc2679e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js
@@ -0,0 +1,75 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState } from '@gitlab/ui';
+import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+import ArtifactsList from '~/packages_and_registries/harbor_registry/components/details/artifacts_list.vue';
+import ArtifactsListRow from '~/packages_and_registries/harbor_registry/components/details/artifacts_list_row.vue';
+import { defaultConfig, harborArtifactsList } from '../../mock_data';
+
+describe('Harbor artifacts list', () => {
+ let wrapper;
+
+ const findTagsLoader = () => wrapper.findComponent(TagsLoader);
+ const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findRegistryList = () => wrapper.findComponent(RegistryList);
+ const findArtifactsListRow = () => wrapper.findAllComponents(ArtifactsListRow);
+
+ const mountComponent = ({ propsData, config = defaultConfig }) => {
+ wrapper = shallowMount(ArtifactsList, {
+ propsData,
+ stubs: { RegistryList },
+ provide() {
+ return {
+ ...config,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when isLoading is true', () => {
+ beforeEach(() => {
+ mountComponent({
+ propsData: {
+ isLoading: true,
+ pageInfo: {},
+ filter: '',
+ artifacts: [],
+ },
+ });
+ });
+
+ it('show the loader', () => {
+ expect(findTagsLoader().exists()).toBe(true);
+ });
+
+ it('does not show the list', () => {
+ expect(findGlEmptyState().exists()).toBe(false);
+ expect(findRegistryList().exists()).toBe(false);
+ });
+ });
+
+ describe('registry list', () => {
+ beforeEach(() => {
+ mountComponent({
+ propsData: {
+ isLoading: false,
+ pageInfo: {},
+ filter: '',
+ artifacts: harborArtifactsList,
+ },
+ });
+ });
+
+ it('exists', () => {
+ expect(findRegistryList().exists()).toBe(true);
+ });
+
+ it('one artifact row exist', () => {
+ expect(findArtifactsListRow()).toHaveLength(harborArtifactsList.length);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js
new file mode 100644
index 00000000000..e8cc2b2e22d
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js
@@ -0,0 +1,85 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import DetailsHeader from '~/packages_and_registries/harbor_registry/components/details/details_header.vue';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { ROOT_IMAGE_TEXT } from '~/packages_and_registries/harbor_registry/constants/index';
+
+describe('Harbor Details Header', () => {
+ let wrapper;
+
+ const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
+ const findTitle = () => findByTestId('title');
+ const findArtifactsCount = () => findByTestId('artifacts-count');
+
+ const mountComponent = ({ propsData }) => {
+ wrapper = shallowMount(DetailsHeader, {
+ propsData,
+ stubs: {
+ TitleArea,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('artifact name', () => {
+ describe('missing image name', () => {
+ beforeEach(() => {
+ mountComponent({ propsData: { imagesDetail: { name: '', artifactCount: 1 } } });
+ });
+
+ it('root image', () => {
+ expect(findTitle().text()).toBe(ROOT_IMAGE_TEXT);
+ });
+ });
+
+ describe('with artifact name present', () => {
+ beforeEach(() => {
+ mountComponent({ propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 1 } } });
+ });
+
+ it('shows artifact.name', () => {
+ expect(findTitle().text()).toContain('shao/flinkx');
+ });
+ });
+ });
+
+ describe('metadata items', () => {
+ describe('artifacts count', () => {
+ it('displays "-- artifacts" while loading', async () => {
+ mountComponent({ propsData: { imagesDetail: {} } });
+ await nextTick();
+
+ expect(findArtifactsCount().props('text')).toBe('-- artifacts');
+ });
+
+ it('when there is more than one artifact has the correct text', async () => {
+ mountComponent({ propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 10 } } });
+
+ await nextTick();
+
+ expect(findArtifactsCount().props('text')).toBe('10 artifacts');
+ });
+
+ it('when there is one artifact has the correct text', async () => {
+ mountComponent({
+ propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 1 } },
+ });
+ await nextTick();
+
+ expect(findArtifactsCount().props('text')).toBe('1 artifact');
+ });
+
+ it('has the correct icon', async () => {
+ mountComponent({
+ propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 1 } },
+ });
+ await nextTick();
+
+ expect(findArtifactsCount().props('icon')).toBe('package');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js
index 636f3eeb04a..7a6169d300c 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js
@@ -7,14 +7,15 @@ import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import {
HARBOR_REGISTRY_TITLE,
LIST_INTRO_TEXT,
+ HARBOR_REGISTRY_HELP_PAGE_PATH,
} from '~/packages_and_registries/harbor_registry/constants/index';
describe('harbor_list_header', () => {
let wrapper;
- const findTitleArea = () => wrapper.find(TitleArea);
+ const findTitleArea = () => wrapper.findComponent(TitleArea);
const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]');
- const findImagesMetaDataItem = () => wrapper.find(MetadataItem);
+ const findImagesMetaDataItem = () => wrapper.findComponent(MetadataItem);
const mountComponent = async (propsData, slots) => {
wrapper = shallowMount(HarborListHeader, {
@@ -77,10 +78,10 @@ describe('harbor_list_header', () => {
describe('info messages', () => {
describe('default message', () => {
it('is correctly bound to title_area props', () => {
- mountComponent({ helpPagePath: 'foo' });
+ mountComponent();
expect(findTitleArea().props('infoMessages')).toEqual([
- { text: LIST_INTRO_TEXT, link: 'foo' },
+ { text: LIST_INTRO_TEXT, link: HARBOR_REGISTRY_HELP_PAGE_PATH },
]);
});
});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js
index 8560c4f78f7..b62d4e8836b 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js
@@ -1,25 +1,24 @@
import { shallowMount, RouterLinkStub as RouterLink } from '@vue/test-utils';
-import { GlIcon, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
+import { GlIcon, GlSkeletonLoader } from '@gitlab/ui';
import HarborListRow from '~/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { harborListResponse } from '../../mock_data';
+import { harborImagesList } from '../../mock_data';
describe('Harbor List Row', () => {
let wrapper;
- const [item] = harborListResponse.repositories;
+ const item = harborImagesList[0];
- const findDetailsLink = () => wrapper.find(RouterLink);
+ const findDetailsLink = () => wrapper.findComponent(RouterLink);
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
- const findTagsCount = () => wrapper.find('[data-testid="tags-count"]');
+ const findArtifactsCount = () => wrapper.find('[data-testid="artifacts-count"]');
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const mountComponent = (props) => {
wrapper = shallowMount(HarborListRow, {
stubs: {
RouterLink,
- GlSprintf,
ListItem,
},
propsData: {
@@ -42,7 +41,8 @@ describe('Harbor List Row', () => {
expect(findDetailsLink().props('to')).toMatchObject({
name: 'details',
params: {
- id: item.id,
+ image: 'nginx',
+ project: 'nginx',
},
});
});
@@ -56,17 +56,17 @@ describe('Harbor List Row', () => {
});
});
- describe('tags count', () => {
+ describe('artifacts count', () => {
it('exists', () => {
mountComponent();
- expect(findTagsCount().exists()).toBe(true);
+ expect(findArtifactsCount().exists()).toBe(true);
});
- it('contains a tag icon', () => {
+ it('contains a package icon', () => {
mountComponent();
- const icon = findTagsCount().find(GlIcon);
+ const icon = findArtifactsCount().findComponent(GlIcon);
expect(icon.exists()).toBe(true);
- expect(icon.props('name')).toBe('tag');
+ expect(icon.props('name')).toBe('package');
});
describe('loading state', () => {
@@ -76,23 +76,23 @@ describe('Harbor List Row', () => {
expect(findSkeletonLoader().exists()).toBe(true);
});
- it('hides the tags count while loading', () => {
+ it('hides the artifacts count while loading', () => {
mountComponent({ metadataLoading: true });
- expect(findTagsCount().exists()).toBe(false);
+ expect(findArtifactsCount().exists()).toBe(false);
});
});
- describe('tags count text', () => {
- it('with one tag in the image', () => {
+ describe('artifacts count text', () => {
+ it('with one artifact in the image', () => {
mountComponent({ item: { ...item, artifactCount: 1 } });
- expect(findTagsCount().text()).toMatchInterpolatedText('1 Tag');
+ expect(findArtifactsCount().text()).toMatchInterpolatedText('1 artifact');
});
- it('with more than one tag in the image', () => {
+ it('with more than one artifact in the image', () => {
mountComponent({ item: { ...item, artifactCount: 3 } });
- expect(findTagsCount().text()).toMatchInterpolatedText('3 Tags');
+ expect(findArtifactsCount().text()).toMatchInterpolatedText('3 artifacts');
});
});
});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js
index f018eff58c9..e7e74a0da58 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js
@@ -2,19 +2,19 @@ import { shallowMount } from '@vue/test-utils';
import HarborList from '~/packages_and_registries/harbor_registry/components/list/harbor_list.vue';
import HarborListRow from '~/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
-import { harborListResponse } from '../../mock_data';
+import { harborImagesList } from '../../mock_data';
describe('Harbor List', () => {
let wrapper;
- const findHarborListRow = () => wrapper.findAll(HarborListRow);
+ const findHarborListRow = () => wrapper.findAllComponents(HarborListRow);
const mountComponent = (props) => {
wrapper = shallowMount(HarborList, {
stubs: { RegistryList },
propsData: {
- images: harborListResponse.repositories,
- pageInfo: harborListResponse.pageInfo,
+ images: harborImagesList,
+ pageInfo: {},
...props,
},
});
@@ -28,7 +28,7 @@ describe('Harbor List', () => {
it('contains one list element for each image', () => {
mountComponent();
- expect(findHarborListRow().length).toBe(harborListResponse.repositories.length);
+ expect(findHarborListRow().length).toBe(harborImagesList.length);
});
it('passes down the metadataLoading prop', () => {
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js
new file mode 100644
index 00000000000..5e299a269e3
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js
@@ -0,0 +1,52 @@
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { mockArtifactDetail, MOCK_SHA_DIGEST } from '../../mock_data';
+
+describe('Harbor Tags Header', () => {
+ let wrapper;
+
+ const findTitle = () => wrapper.findByTestId('title');
+ const findTagsCount = () => wrapper.findByTestId('tags-count');
+
+ const mountComponent = ({ propsData }) => {
+ wrapper = shallowMountExtended(TagsHeader, {
+ propsData,
+ stubs: {
+ TitleArea,
+ },
+ });
+ };
+
+ const mockPageInfo = {
+ page: 1,
+ perPage: 20,
+ total: 1,
+ totalPages: 1,
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ mountComponent({
+ propsData: { artifactDetail: mockArtifactDetail, pageInfo: mockPageInfo, tagsLoading: false },
+ });
+ });
+
+ describe('tags title', () => {
+ it('should be artifact digest', () => {
+ expect(findTitle().text()).toBe(`sha256:${MOCK_SHA_DIGEST}`);
+ });
+ });
+
+ describe('tags count', () => {
+ it('would has the correct text', async () => {
+ await nextTick();
+
+ expect(findTagsCount().props('text')).toBe('1 tag');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js
new file mode 100644
index 00000000000..6fe3dabc603
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js
@@ -0,0 +1,75 @@
+import { GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import TagsListRow from '~/packages_and_registries/harbor_registry/components/tags/tags_list_row.vue';
+import { defaultConfig, harborTagsList } from '../../mock_data';
+
+describe('Harbor tag list row', () => {
+ let wrapper;
+
+ const findListItem = () => wrapper.find(ListItem);
+ const findClipboardButton = () => wrapper.find(ClipboardButton);
+ const findByTestId = (testId) => wrapper.findByTestId(testId);
+
+ const $route = {
+ params: {
+ project: defaultConfig.harborIntegrationProjectName,
+ image: 'test-repository',
+ },
+ };
+
+ const mountComponent = ({ propsData, config = defaultConfig }) => {
+ wrapper = shallowMountExtended(TagsListRow, {
+ stubs: {
+ ListItem,
+ GlSprintf,
+ },
+ propsData,
+ mocks: {
+ $route,
+ },
+ provide() {
+ return {
+ ...config,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('list item', () => {
+ beforeEach(() => {
+ mountComponent({
+ propsData: {
+ tag: harborTagsList[0],
+ },
+ });
+ });
+
+ it('exists', () => {
+ expect(findListItem().exists()).toBe(true);
+ });
+
+ it('has the correct tag name', () => {
+ expect(findByTestId('name').text()).toBe(harborTagsList[0].name);
+ });
+
+ describe(' clipboard button', () => {
+ it('exists', () => {
+ expect(findClipboardButton().exists()).toBe(true);
+ });
+
+ it('has the correct props', () => {
+ const pullCommand = `docker pull demo.harbor.com/test-project/test-repository:${harborTagsList[0].name}`;
+ expect(findClipboardButton().attributes()).toMatchObject({
+ text: pullCommand,
+ title: pullCommand,
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js
new file mode 100644
index 00000000000..6bcf6611d07
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js
@@ -0,0 +1,66 @@
+import { shallowMount } from '@vue/test-utils';
+import TagsList from '~/packages_and_registries/harbor_registry/components/tags/tags_list.vue';
+import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
+import TagsListRow from '~/packages_and_registries/harbor_registry/components/tags/tags_list_row.vue';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+import { defaultConfig, harborTagsResponse } from '../../mock_data';
+
+describe('Harbor Tags List', () => {
+ let wrapper;
+
+ const findTagsLoader = () => wrapper.find(TagsLoader);
+ const findTagsListRows = () => wrapper.findAllComponents(TagsListRow);
+ const findRegistryList = () => wrapper.find(RegistryList);
+
+ const mountComponent = ({ propsData, config = defaultConfig }) => {
+ wrapper = shallowMount(TagsList, {
+ propsData,
+ stubs: { RegistryList },
+ provide() {
+ return {
+ ...config,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when isLoading is true', () => {
+ beforeEach(() => {
+ mountComponent({
+ propsData: {
+ isLoading: true,
+ pageInfo: {},
+ tags: [],
+ },
+ });
+ });
+
+ it('show the loader', () => {
+ expect(findTagsLoader().exists()).toBe(true);
+ });
+ });
+
+ describe('tags list', () => {
+ beforeEach(() => {
+ mountComponent({
+ propsData: {
+ isLoading: false,
+ pageInfo: {},
+ tags: harborTagsResponse,
+ },
+ });
+ });
+
+ it('should render correctly', () => {
+ expect(findRegistryList().exists()).toBe(true);
+ });
+
+ it('one tag row exists', () => {
+ expect(findTagsListRows()).toHaveLength(harborTagsResponse.length);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/mock_data.js b/spec/frontend/packages_and_registries/harbor_registry/mock_data.js
index 85399c22e79..b8989b6092e 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/mock_data.js
@@ -1,175 +1,114 @@
-export const harborListResponse = {
- repositories: [
- {
- artifactCount: 1,
- creationTime: '2022-03-02T06:35:53.205Z',
- id: 25,
- name: 'shao/flinkx',
- projectId: 21,
- pullCount: 0,
- updateTime: '2022-03-02T06:35:53.205Z',
- location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
- },
- {
- artifactCount: 1,
- creationTime: '2022-03-02T06:35:53.205Z',
- id: 26,
- name: 'shao/flinkx1',
- projectId: 21,
- pullCount: 0,
- updateTime: '2022-03-02T06:35:53.205Z',
- location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
- },
- {
- artifactCount: 1,
- creationTime: '2022-03-02T06:35:53.205Z',
- id: 27,
- name: 'shao/flinkx2',
- projectId: 21,
- pullCount: 0,
- updateTime: '2022-03-02T06:35:53.205Z',
- location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
- },
- ],
- totalCount: 3,
- pageInfo: {
- hasNextPage: false,
- hasPreviousPage: false,
- },
+export const harborImageDetailEmptyResponse = {
+ data: null,
};
-export const harborTagsResponse = {
- tags: [
- {
- digest: 'sha256:7f386a1844faf341353e1c20f2f39f11f397604fedc475435d13f756eeb235d1',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c',
- name: '02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c',
- revision: 'f53bde3d44699e04e11cf15fb415046a0913e2623d878d89bc21adb2cbda5255',
- shortRevision: 'f53bde3d4',
- createdAt: '2022-03-02T23:59:05+00:00',
- totalSize: '6623124',
- },
- {
- digest: 'sha256:4554416b84c4568fe93086620b637064ed029737aabe7308b96d50e3d9d92ed7',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160',
- name: '02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160',
- revision: 'e1fe52d8bab66d71bd54a6b8784d3b9edbc68adbd6ea87f5fa44d9974144ef9e',
- shortRevision: 'e1fe52d8b',
- createdAt: '2022-02-10T01:09:56+00:00',
- totalSize: '920760',
- },
- {
- digest: 'sha256:14f37b60e52b9ce0e9f8f7094b311d265384798592f783487c30aaa3d58e6345',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a',
- name: '03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a',
- revision: 'c72770c6eb93c421bc496964b4bffc742b1ec2e642cdab876be7afda1856029f',
- shortRevision: 'c72770c6e',
- createdAt: '2021-12-22T04:48:48+00:00',
- totalSize: '48609053',
- },
- {
- digest: 'sha256:e925e3b8277ea23f387ed5fba5e78280cfac7cfb261a78cf046becf7b6a3faae',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19',
- name: '03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19',
- revision: '1ac2a43194f4e15166abdf3f26e6ec92215240490b9cac834d63de1a3d87494a',
- shortRevision: '1ac2a4319',
- createdAt: '2022-03-09T11:02:27+00:00',
- totalSize: '35141894',
- },
- {
- digest: 'sha256:7d8303fd5c077787a8c879f8f66b69e2b5605f48ccd3f286e236fb0749fcc1ca',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda',
- name: '05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda',
- revision: 'cf8fee086701016e1a84e6824f0c896951fef4cce9d4745459558b87eec3232c',
- shortRevision: 'cf8fee086',
- createdAt: '2022-01-21T11:31:43+00:00',
- totalSize: '48716070',
- },
- {
- digest: 'sha256:b33611cefe20e4a41a6e0dce356a5d7ef3c177ea7536a58652f5b3a4f2f83549',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a',
- name: '093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a',
- revision: '1a4b48198b13d55242c5164e64d41c4e9f75b5d9506bc6e0efc1534dd0dd1f15',
- shortRevision: '1a4b48198',
- createdAt: '2022-01-21T11:31:51+00:00',
- totalSize: '6623127',
- },
- {
- digest: 'sha256:d25c3c020e2dbd4711a67b9fe308f4cbb7b0bb21815e722a02f91c570dc5d519',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7',
- name: '09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7',
- revision: '03e2e2777dde01c30469ee8c710973dd08a7a4f70494d7dc1583c24b525d7f61',
- shortRevision: '03e2e2777',
- createdAt: '2022-03-02T23:58:20+00:00',
- totalSize: '911377',
- },
- {
- digest: 'sha256:fb760e4d2184e9e8e39d6917534d4610fe01009734698a5653b2de1391ba28f4',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95',
- name: '09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95',
- revision: '350e78d60646bf6967244448c6aaa14d21ecb9a0c6cf87e9ff0361cbe59b9012',
- shortRevision: '350e78d60',
- createdAt: '2022-01-19T13:49:14+00:00',
- totalSize: '48710241',
- },
- {
- digest: 'sha256:407250f380cea92729cbc038c420e74900f53b852e11edc6404fe75a0fd2c402',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557',
- name: '0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557',
- revision: '76038370b7f3904364891457c4a6a234897255e6b9f45d0a852bf3a7e5257e18',
- shortRevision: '76038370b',
- createdAt: '2022-01-24T12:56:22+00:00',
- totalSize: '280065',
- },
- {
- digest: 'sha256:ada87f25218542951ce6720c27f3d0758e90c2540bd129f5cfb9e15b31e07b07',
- location:
- 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb',
- path:
- 'gitlab-org/gitlab/gitlab-ee-qa/cache:0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb',
- name: '0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb',
- revision: '3d4b49a7bbb36c48bb721f4d0e76e7950bec3878ee29cdfdd6da39f575d6d37f',
- shortRevision: '3d4b49a7b',
- createdAt: '2022-02-17T17:37:52+00:00',
- totalSize: '48655767',
- },
- ],
- totalCount: 100,
- pageInfo: {
- hasNextPage: false,
- hasPreviousPage: false,
+export const MOCK_SHA_DIGEST = 'mock_sha_digest_value';
+
+export const harborImageDetailResponse = {
+ artifactCount: 10,
+ creationTime: '2022-03-02T06:35:53.205Z',
+ id: 25,
+ name: 'shao/flinkx',
+ projectId: 21,
+ pullCount: 0,
+ updateTime: '2022-03-02T06:35:53.205Z',
+ location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
+};
+
+export const harborArtifactsResponse = [
+ {
+ id: 1,
+ digest: `sha256:${MOCK_SHA_DIGEST}`,
+ size: 773928,
+ push_time: '2022-05-19T15:54:47.821Z',
+ tags: ['latest'],
+ },
+];
+
+export const harborArtifactsList = [
+ {
+ id: 1,
+ digest: `sha256:${MOCK_SHA_DIGEST}`,
+ size: 773928,
+ pushTime: '2022-05-19T15:54:47.821Z',
+ tags: ['latest'],
+ },
+];
+
+export const harborTagsResponse = [
+ {
+ repository_id: 4,
+ artifact_id: 5,
+ id: 4,
+ name: 'latest',
+ pull_time: '0001-01-01T00:00:00.000Z',
+ push_time: '2022-05-27T18:21:27.903Z',
+ signed: false,
+ immutable: false,
+ },
+];
+
+export const harborTagsList = [
+ {
+ repositoryId: 4,
+ artifactId: 5,
+ id: 4,
+ name: 'latest',
+ pullTime: '0001-01-01T00:00:00.000Z',
+ pushTime: '2022-05-27T18:21:27.903Z',
+ signed: false,
+ immutable: false,
},
+];
+
+export const defaultConfig = {
+ noContainersImage: 'noContainersImage',
+ repositoryUrl: 'demo.harbor.com',
+ harborIntegrationProjectName: 'test-project',
+ projectName: 'Flight',
+ endpoint: '/flightjs/Flight/-/harbor/repositories',
+ connectionError: false,
+ invalidPathError: false,
+ isGroupPage: false,
+ containersErrorImage: 'containersErrorImage',
};
+export const defaultFullPath = 'flightjs/Flight';
+
+export const harborImagesResponse = [
+ {
+ id: 1,
+ name: 'nginx/nginx',
+ artifact_count: 1,
+ creation_time: '2022-05-29T10:07:16.812Z',
+ update_time: '2022-05-29T10:07:16.812Z',
+ project_id: 4,
+ pull_count: 0,
+ location: 'https://demo.goharbor.io/harbor/projects/4/repositories/nginx',
+ },
+];
+
+export const harborImagesList = [
+ {
+ id: 1,
+ name: 'nginx/nginx',
+ artifactCount: 1,
+ creationTime: '2022-05-29T10:07:16.812Z',
+ updateTime: '2022-05-29T10:07:16.812Z',
+ projectId: 4,
+ pullCount: 0,
+ location: 'https://demo.goharbor.io/harbor/projects/4/repositories/nginx',
+ },
+];
+
export const dockerCommands = {
dockerBuildCommand: 'foofoo',
dockerPushCommand: 'barbar',
dockerLoginCommand: 'bazbaz',
};
+
+export const mockArtifactDetail = {
+ project: 'test-project',
+ image: 'test-repository',
+ digest: `sha256:${MOCK_SHA_DIGEST}`,
+};
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js
new file mode 100644
index 00000000000..8fd50bea280
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js
@@ -0,0 +1,162 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { GlFilteredSearchToken } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import HarborDetailsPage from '~/packages_and_registries/harbor_registry/pages/details.vue';
+import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
+import ArtifactsList from '~/packages_and_registries/harbor_registry/components/details/artifacts_list.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import DetailsHeader from '~/packages_and_registries/harbor_registry/components/details/details_header.vue';
+import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
+import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
+import {
+ NAME_SORT_FIELD,
+ TOKEN_TYPE_TAG_NAME,
+} from '~/packages_and_registries/harbor_registry/constants/index';
+import { harborArtifactsResponse, harborArtifactsList, defaultConfig } from '../mock_data';
+
+let mockHarborArtifactsResponse;
+
+jest.mock('~/rest_api', () => ({
+ getHarborArtifacts: () => mockHarborArtifactsResponse,
+}));
+
+describe('Harbor Details Page', () => {
+ let wrapper;
+
+ const findTagsLoader = () => wrapper.findComponent(TagsLoader);
+ const findArtifactsList = () => wrapper.findComponent(ArtifactsList);
+ const findDetailsHeader = () => wrapper.findComponent(DetailsHeader);
+ const findPersistedSearch = () => wrapper.findComponent(PersistedSearch);
+
+ const waitForHarborDetailRequest = async () => {
+ await waitForPromises();
+ await nextTick();
+ };
+
+ const $route = {
+ params: {
+ project: 'test-project',
+ image: 'test-repository',
+ },
+ };
+
+ const breadCrumbState = {
+ updateName: jest.fn(),
+ updateHref: jest.fn(),
+ };
+
+ const defaultHeaders = {
+ 'x-page': '1',
+ 'X-Per-Page': '20',
+ 'X-TOTAL': '1',
+ 'X-Total-Pages': '1',
+ };
+
+ const mountComponent = ({ config = defaultConfig } = {}) => {
+ wrapper = shallowMount(HarborDetailsPage, {
+ mocks: {
+ $route,
+ },
+ provide() {
+ return {
+ breadCrumbState,
+ ...config,
+ };
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockHarborArtifactsResponse = Promise.resolve({
+ data: harborArtifactsResponse,
+ headers: defaultHeaders,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when isLoading is true', () => {
+ it('shows the loader', () => {
+ mountComponent();
+
+ expect(findTagsLoader().exists()).toBe(true);
+ });
+
+ it('does not show the list', () => {
+ mountComponent();
+
+ expect(findArtifactsList().exists()).toBe(false);
+ });
+ });
+
+ describe('artifacts list', () => {
+ it('exists', async () => {
+ mountComponent();
+
+ findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] });
+ await waitForHarborDetailRequest();
+
+ expect(findArtifactsList().exists()).toBe(true);
+ });
+
+ it('has the correct props bound', async () => {
+ mountComponent();
+
+ findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] });
+ await waitForHarborDetailRequest();
+
+ expect(findArtifactsList().props()).toMatchObject({
+ isLoading: false,
+ filter: '',
+ artifacts: harborArtifactsList,
+ pageInfo: {
+ page: 1,
+ perPage: 20,
+ total: 1,
+ totalPages: 1,
+ },
+ });
+ });
+ });
+
+ describe('persisted search', () => {
+ it('has the correct props', () => {
+ mountComponent();
+
+ expect(findPersistedSearch().props()).toMatchObject({
+ sortableFields: [NAME_SORT_FIELD],
+ defaultOrder: NAME_SORT_FIELD.orderBy,
+ defaultSort: 'asc',
+ tokens: [
+ {
+ type: TOKEN_TYPE_TAG_NAME,
+ icon: 'tag',
+ title: s__('HarborRegistry|Tag'),
+ unique: true,
+ token: GlFilteredSearchToken,
+ operators: OPERATOR_IS_ONLY,
+ },
+ ],
+ });
+ });
+ });
+
+ describe('header', () => {
+ it('has the correct props', async () => {
+ mountComponent();
+
+ findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] });
+ await waitForHarborDetailRequest();
+
+ expect(findDetailsHeader().props()).toMatchObject({
+ imagesDetail: {
+ name: 'test-project/test-repository',
+ artifactCount: 1,
+ },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js
index 55fc8066f65..942cf9bad2c 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js
@@ -4,7 +4,7 @@ import component from '~/packages_and_registries/harbor_registry/pages/index.vue
describe('List Page', () => {
let wrapper;
- const findRouterView = () => wrapper.find({ ref: 'router-view' });
+ const findRouterView = () => wrapper.findComponent({ ref: 'router-view' });
const mountComponent = () => {
wrapper = shallowMount(component, {
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js
index 61ee36a2794..97d30e6fe99 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js
@@ -5,15 +5,14 @@ import HarborListHeader from '~/packages_and_registries/harbor_registry/componen
import HarborRegistryList from '~/packages_and_registries/harbor_registry/pages/list.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import waitForPromises from 'helpers/wait_for_promises';
-// import { harborListResponse } from '~/packages_and_registries/harbor_registry/mock_api.js';
import HarborList from '~/packages_and_registries/harbor_registry/components/list/harbor_list.vue';
import CliCommands from '~/packages_and_registries/shared/components/cli_commands.vue';
import { SORT_FIELDS } from '~/packages_and_registries/harbor_registry/constants/index';
-import { harborListResponse, dockerCommands } from '../mock_data';
+import { harborImagesResponse, defaultConfig, harborImagesList } from '../mock_data';
let mockHarborListResponse;
-jest.mock('~/packages_and_registries/harbor_registry/mock_api.js', () => ({
- harborListResponse: () => mockHarborListResponse,
+jest.mock('~/rest_api', () => ({
+ getHarborRepositoriesList: () => mockHarborListResponse,
}));
describe('Harbor List Page', () => {
@@ -24,34 +23,43 @@ describe('Harbor List Page', () => {
await nextTick();
};
- beforeEach(() => {
- mockHarborListResponse = Promise.resolve(harborListResponse);
- });
-
const findHarborListHeader = () => wrapper.findComponent(HarborListHeader);
const findPersistedSearch = () => wrapper.findComponent(PersistedSearch);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findHarborList = () => wrapper.findComponent(HarborList);
const findCliCommands = () => wrapper.findComponent(CliCommands);
+ const defaultHeaders = {
+ 'x-page': '1',
+ 'X-Per-Page': '20',
+ 'X-TOTAL': '1',
+ 'X-Total-Pages': '1',
+ };
+
const fireFirstSortUpdate = () => {
findPersistedSearch().vm.$emit('update', { sort: 'UPDATED_DESC', filters: [] });
};
- const mountComponent = ({ config = { isGroupPage: false } } = {}) => {
+ const mountComponent = ({ config = defaultConfig } = {}) => {
wrapper = shallowMount(HarborRegistryList, {
stubs: {
HarborListHeader,
},
provide() {
return {
- config,
- ...dockerCommands,
+ ...config,
};
},
});
};
+ beforeEach(() => {
+ mockHarborListResponse = Promise.resolve({
+ data: harborImagesResponse,
+ headers: defaultHeaders,
+ });
+ });
+
afterEach(() => {
wrapper.destroy();
});
@@ -64,7 +72,7 @@ describe('Harbor List Page', () => {
expect(findHarborListHeader().exists()).toBe(true);
expect(findHarborListHeader().props()).toMatchObject({
- imagesCount: 3,
+ imagesCount: 1,
metadataLoading: false,
});
});
@@ -117,6 +125,16 @@ describe('Harbor List Page', () => {
await nextTick();
expect(findHarborList().exists()).toBe(true);
+ expect(findHarborList().props()).toMatchObject({
+ images: harborImagesList,
+ metadataLoading: false,
+ pageInfo: {
+ page: 1,
+ perPage: 20,
+ total: 1,
+ totalPages: 1,
+ },
+ });
});
});
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js
new file mode 100644
index 00000000000..7e0f05e736b
--- /dev/null
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js
@@ -0,0 +1,125 @@
+import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import HarborTagsPage from '~/packages_and_registries/harbor_registry/pages/harbor_tags.vue';
+import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue';
+import TagsList from '~/packages_and_registries/harbor_registry/components/tags/tags_list.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import { defaultConfig, harborTagsResponse, mockArtifactDetail } from '../mock_data';
+
+let mockHarborTagsResponse;
+
+jest.mock('~/rest_api', () => ({
+ getHarborTags: () => mockHarborTagsResponse,
+}));
+
+describe('Harbor Tags page', () => {
+ let wrapper;
+
+ const findTagsHeader = () => wrapper.find(TagsHeader);
+ const findTagsList = () => wrapper.find(TagsList);
+
+ const waitForHarborTagsRequest = async () => {
+ await waitForPromises();
+ await nextTick();
+ };
+
+ const breadCrumbState = {
+ updateName: jest.fn(),
+ updateHref: jest.fn(),
+ };
+
+ const $route = {
+ params: mockArtifactDetail,
+ };
+
+ const defaultHeaders = {
+ 'x-page': '1',
+ 'X-Per-Page': '20',
+ 'X-TOTAL': '1',
+ 'X-Total-Pages': '1',
+ };
+
+ const mountComponent = ({ endpoint = defaultConfig.endpoint } = {}) => {
+ wrapper = shallowMount(HarborTagsPage, {
+ mocks: {
+ $route,
+ },
+ provide() {
+ return {
+ breadCrumbState,
+ endpoint,
+ };
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockHarborTagsResponse = Promise.resolve({
+ data: harborTagsResponse,
+ headers: defaultHeaders,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('contains tags header', () => {
+ mountComponent();
+
+ expect(findTagsHeader().exists()).toBe(true);
+ });
+
+ it('contains tags list', () => {
+ mountComponent();
+
+ expect(findTagsList().exists()).toBe(true);
+ });
+
+ describe('header', () => {
+ it('has the correct props', async () => {
+ mountComponent();
+
+ await waitForHarborTagsRequest();
+ expect(findTagsHeader().props()).toMatchObject({
+ artifactDetail: mockArtifactDetail,
+ pageInfo: {
+ page: 1,
+ perPage: 20,
+ total: 1,
+ totalPages: 1,
+ },
+ tagsLoading: false,
+ });
+ });
+ });
+
+ describe('list', () => {
+ it('has the correct props', async () => {
+ mountComponent();
+
+ await waitForHarborTagsRequest();
+ expect(findTagsList().props()).toMatchObject({
+ tags: [
+ {
+ repositoryId: 4,
+ artifactId: 5,
+ id: 4,
+ name: 'latest',
+ pullTime: '0001-01-01T00:00:00.000Z',
+ pushTime: '2022-05-27T18:21:27.903Z',
+ signed: false,
+ immutable: false,
+ },
+ ],
+ isLoading: false,
+ pageInfo: {
+ page: 1,
+ perPage: 20,
+ total: 1,
+ totalPages: 1,
+ },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
index 69c78e64e22..e74375b7705 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
@@ -76,8 +76,8 @@ describe('PackagesApp', () => {
const packageTitle = () => wrapper.findComponent(TerraformTitle);
const emptyState = () => wrapper.findComponent(GlEmptyState);
const deleteButton = () => wrapper.find('.js-delete-button');
- const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
- const findDeleteFileModal = () => wrapper.find({ ref: 'deleteFileModal' });
+ const findDeleteModal = () => wrapper.findComponent({ ref: 'deleteModal' });
+ const findDeleteFileModal = () => wrapper.findComponent({ ref: 'deleteFileModal' });
const versionsTab = () => wrapper.find('.js-versions-tab > a');
const packagesLoader = () => wrapper.findComponent(PackagesListLoader);
const packagesVersionRows = () => wrapper.findAllComponents(PackageListRow);
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
index 95de2f0bb0b..b76d7c2b57b 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
@@ -17,8 +17,8 @@ describe('Package Files', () => {
const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"]');
const findFirstRowCommitLink = () => findFirstRow().find('[data-testid="commit-link"]');
const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"]');
- const findFirstRowFileIcon = () => findFirstRow().find(FileIcon);
- const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
+ const findFirstRowFileIcon = () => findFirstRow().findComponent(FileIcon);
+ const findFirstRowCreatedAt = () => findFirstRow().findComponent(TimeAgoTooltip);
const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton);
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
index f10f05f4a0d..c6b5138639e 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
@@ -36,8 +36,8 @@ describe('Package History', () => {
});
const findHistoryElement = (testId) => wrapper.find(`[data-testid="${testId}"]`);
- const findElementLink = (container) => container.find(GlLink);
- const findElementTimeAgo = (container) => container.find(TimeAgoTooltip);
+ const findElementLink = (container) => container.findComponent(GlLink);
+ const findElementTimeAgo = (container) => container.findComponent(TimeAgoTooltip);
const findTitle = () => wrapper.find('[data-testid="title"]');
const findTimeline = () => wrapper.find('[data-testid="timeline"]');
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
index 72d08d5683b..93d013bb458 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
@@ -7,8 +7,8 @@ describe('Infrastructure Title', () => {
let wrapper;
let store;
- const findTitleArea = () => wrapper.find(TitleArea);
- const findMetadataItem = () => wrapper.find(MetadataItem);
+ const findTitleArea = () => wrapper.findComponent(TitleArea);
+ const findMetadataItem = () => wrapper.findComponent(MetadataItem);
const exampleProps = { helpUrl: 'http://example.gitlab.com/help' };
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
index 31616e0b2f5..db1d3f3f633 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
@@ -31,9 +31,9 @@ describe('packages_list_app', () => {
const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' };
const emptyListHelpUrl = 'helpUrl';
- const findEmptyState = () => wrapper.find(GlEmptyState);
- const findListComponent = () => wrapper.find(PackageList);
- const findInfrastructureSearch = () => wrapper.find(InfrastructureSearch);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findListComponent = () => wrapper.findComponent(PackageList);
+ const findInfrastructureSearch = () => wrapper.findComponent(InfrastructureSearch);
const createStore = ({ filter = [], packageCount = 0 } = {}) => {
store = new Vuex.Store({
@@ -151,7 +151,7 @@ describe('packages_list_app', () => {
describe('empty state', () => {
it('generate the correct empty list link', () => {
- const link = findListComponent().find(GlLink);
+ const link = findListComponent().findComponent(GlLink);
expect(link.attributes('href')).toBe(emptyListHelpUrl);
expect(link.text()).toBe('publish and share your packages');
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
index fed82653016..fb5ee4e6884 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
@@ -20,11 +20,11 @@ describe('packages_list', () => {
const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' };
- const findPackagesListLoader = () => wrapper.find(PackagesListLoader);
- const findPackageListPagination = () => wrapper.find(GlPagination);
- const findPackageListDeleteModal = () => wrapper.find(GlModal);
- const findEmptySlot = () => wrapper.find(EmptySlotStub);
- const findPackagesListRow = () => wrapper.find(PackagesListRow);
+ const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
+ const findPackageListPagination = () => wrapper.findComponent(GlPagination);
+ const findPackageListDeleteModal = () => wrapper.findComponent(GlModal);
+ const findEmptySlot = () => wrapper.findComponent(EmptySlotStub);
+ const findPackagesListRow = () => wrapper.findComponent(PackagesListRow);
const createStore = (isGroupPage, packages, isLoading) => {
const state = {
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
index 67c3b8b795a..91824dee5b0 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
@@ -3,7 +3,7 @@
exports[`packages_list_row renders 1`] = `
<div
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
- data-qa-selector="package_row"
+ data-testid="package-row"
>
<div
class="gl-display-flex gl-align-items-center gl-py-3"
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
index abb0d23b6e4..db90bb4c25f 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
@@ -5,7 +5,7 @@ import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_
describe('InfrastructureIconAndName', () => {
let wrapper;
- const findIcon = () => wrapper.find(GlIcon);
+ const findIcon = () => wrapper.findComponent(GlIcon);
const mountComponent = () => {
wrapper = shallowMount(InfrastructureIconAndName, {});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
index d324d43258c..9449c40c7c6 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
@@ -71,7 +71,7 @@ describe('NugetInstallation', () => {
});
});
- it('it has docs link', () => {
+ it('has docs link', () => {
expect(findSetupDocsLink().attributes()).toMatchObject({
href: NUGET_HELP_PATH,
target: '_blank',
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap
index 031afa62890..5be05ddf629 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap
@@ -3,7 +3,7 @@
exports[`packages_list_row renders 1`] = `
<div
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
- data-qa-selector="package_row"
+ data-testid="package-row"
>
<div
class="gl-display-flex gl-align-items-center gl-py-3"
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
index eb1e76377ff..b5a512b8806 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
@@ -30,10 +30,10 @@ describe('packages_list_row', () => {
const packageWithTags = { ...packageWithoutTags, tags: { nodes: packageTags() } };
const packageCannotDestroy = { ...packageData(), canDestroy: false };
- const findPackageTags = () => wrapper.find(PackageTags);
- const findPackagePath = () => wrapper.find(PackagePath);
+ const findPackageTags = () => wrapper.findComponent(PackageTags);
+ const findPackagePath = () => wrapper.findComponent(PackagePath);
const findDeleteDropdown = () => wrapper.findByTestId('action-delete');
- const findPackageIconAndName = () => wrapper.find(PackageIconAndName);
+ const findPackageIconAndName = () => wrapper.findComponent(PackageIconAndName);
const findPackageLink = () => wrapper.findByTestId('details-link');
const findWarningIcon = () => wrapper.findByTestId('warning-icon');
const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos');
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
index 660f00a2b31..3e3607a361c 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
@@ -190,7 +190,7 @@ describe('packages_list', () => {
});
});
- describe('pagination ', () => {
+ describe('pagination', () => {
beforeEach(() => {
mountComponent({ pageInfo: { hasPreviousPage: true } });
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
index 23e5c7330d5..b47515e15c3 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
@@ -7,8 +7,8 @@ describe('PackageTitle', () => {
let wrapper;
let store;
- const findTitleArea = () => wrapper.find(TitleArea);
- const findMetadataItem = () => wrapper.find(MetadataItem);
+ const findTitleArea = () => wrapper.findComponent(TitleArea);
+ const findMetadataItem = () => wrapper.findComponent(MetadataItem);
const mountComponent = (propsData = { helpUrl: 'foo' }) => {
wrapper = shallowMount(PackageTitle, {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
index d0c111bae2d..8f3c8667c47 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
@@ -6,8 +6,8 @@ import { PACKAGE_TYPES } from '~/packages_and_registries/package_registry/consta
describe('packages_filter', () => {
let wrapper;
- const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
- const findFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
+ const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+ const findFilteredSearchSuggestions = () => wrapper.findAllComponents(GlFilteredSearchSuggestion);
const mountComponent = ({ attrs, listeners } = {}) => {
wrapper = shallowMount(component, {
@@ -24,13 +24,13 @@ describe('packages_filter', () => {
wrapper = null;
});
- it('it binds all of his attrs to filtered search token', () => {
+ 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', () => {
+ it('binds all of his events to filtered search token', () => {
const clickListener = jest.fn();
mountComponent({ listeners: { click: clickListener } });
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
index de78e6bb87b..83158d1cc5e 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
@@ -178,7 +178,7 @@ describe('PackagesApp', () => {
${PACKAGE_TYPE_PYPI} | ${true}
${PACKAGE_TYPE_NPM} | ${false}
`(
- `It is $visible that the component is visible when the package is $packageType`,
+ `is $visible that the component is visible when the package is $packageType`,
async ({ packageType, visible }) => {
createComponent({
resolver: jest.fn().mockResolvedValue(
@@ -328,8 +328,8 @@ describe('PackagesApp', () => {
findPackageFiles().vm.$emit('delete-files', [fileToDelete]);
- expect(showDeletePackageSpy).not.toBeCalled();
- expect(showDeleteFileSpy).toBeCalled();
+ expect(showDeletePackageSpy).not.toHaveBeenCalled();
+ expect(showDeleteFileSpy).toHaveBeenCalled();
});
it('when its the only file opens delete package confirmation modal', async () => {
@@ -357,8 +357,8 @@ describe('PackagesApp', () => {
findPackageFiles().vm.$emit('delete-files', [fileToDelete]);
- expect(showDeletePackageSpy).toBeCalled();
- expect(showDeleteFileSpy).not.toBeCalled();
+ expect(showDeletePackageSpy).toHaveBeenCalled();
+ expect(showDeleteFileSpy).not.toHaveBeenCalled();
});
it('confirming on the modal sets the loading state', async () => {
@@ -443,7 +443,7 @@ describe('PackagesApp', () => {
findPackageFiles().vm.$emit('delete-files', packageFiles());
- expect(showDeleteFilesSpy).toBeCalled();
+ expect(showDeleteFilesSpy).toHaveBeenCalled();
});
it('confirming on the modal sets the loading state', async () => {
@@ -532,7 +532,7 @@ describe('PackagesApp', () => {
findPackageFiles().vm.$emit('delete-files', packageFiles());
- expect(showDeletePackageSpy).toBeCalled();
+ expect(showDeletePackageSpy).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
deleted file mode 100644
index 5b56cb7f74e..00000000000
--- a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
+++ /dev/null
@@ -1,18 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`settings_titles renders properly 1`] = `
-<div>
- <h5
- class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3"
- >
-
- foo
-
- </h5>
-
- <p>
- bar
- </p>
-
-</div>
-`;
diff --git a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
index 9d4c7f4737b..796d89231f4 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
@@ -169,7 +169,7 @@ describe('DependencyProxySettings', () => {
toggleName | toggleFinder | localErrorMock | optimisticResponse
${'enable proxy'} | ${findEnableProxyToggle} | ${dependencyProxySettingMutationMock} | ${updateGroupDependencyProxySettingsOptimisticResponse}
${'enable ttl policies'} | ${findEnableTtlPoliciesToggle} | ${dependencyProxyUpdateTllPolicyMutationMock} | ${updateDependencyProxyImageTtlGroupPolicyOptimisticResponse}
- `('$toggleName settings update ', ({ optimisticResponse, toggleFinder, localErrorMock }) => {
+ `('$toggleName settings update', ({ optimisticResponse, toggleFinder, localErrorMock }) => {
describe('success state', () => {
it('emits a success event', async () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js
deleted file mode 100644
index 3eecdeb5b1f..00000000000
--- a/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import component from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
-
-import {
- DUPLICATES_TOGGLE_LABEL,
- DUPLICATES_SETTING_EXCEPTION_TITLE,
- DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
-} from '~/packages_and_registries/settings/group/constants';
-
-describe('Duplicates Settings', () => {
- let wrapper;
-
- const defaultProps = {
- duplicatesAllowed: false,
- duplicateExceptionRegex: 'foo',
- modelNames: {
- allowed: 'allowedModel',
- exception: 'exceptionModel',
- },
- };
-
- const mountComponent = (propsData = defaultProps) => {
- wrapper = shallowMount(component, {
- propsData,
- stubs: {
- GlSprintf,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findToggle = () => wrapper.findComponent(GlToggle);
-
- const findInputGroup = () => wrapper.findComponent(GlFormGroup);
- const findInput = () => wrapper.findComponent(GlFormInput);
-
- it('has a toggle', () => {
- mountComponent();
-
- expect(findToggle().exists()).toBe(true);
- expect(findToggle().props()).toMatchObject({
- label: DUPLICATES_TOGGLE_LABEL,
- value: !defaultProps.duplicatesAllowed,
- });
- });
-
- it('toggle emits an update event', () => {
- mountComponent();
-
- findToggle().vm.$emit('change', false);
-
- expect(wrapper.emitted('update')).toStrictEqual([
- [{ [defaultProps.modelNames.allowed]: true }],
- ]);
- });
-
- describe('when the duplicates are 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: DUPLICATES_SETTING_EXCEPTION_TITLE,
- description: DUPLICATES_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.duplicateExceptionRegex,
- });
- });
-
- it('input change event emits an update event', () => {
- mountComponent();
-
- findInput().vm.$emit('change', 'bar');
-
- expect(wrapper.emitted('update')).toStrictEqual([
- [{ [defaultProps.modelNames.exception]: '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,
- duplicateExceptionRegexError: 'some error string',
- };
-
- mountComponent(propsWithError);
-
- expect(findInputGroup().attributes()).toMatchObject({
- 'invalid-feedback': propsWithError.duplicateExceptionRegexError,
- });
- });
- });
- });
-
- describe('when the duplicates are enabled', () => {
- it('hides the form input group', () => {
- mountComponent({ ...defaultProps, duplicatesAllowed: true });
-
- expect(findInputGroup().exists()).toBe(false);
- });
- });
-
- describe('loading', () => {
- beforeEach(() => {
- mountComponent({ ...defaultProps, loading: true });
- });
-
- it('disables the enable toggle', () => {
- expect(findToggle().props('disabled')).toBe(true);
- });
-
- it('disables the form input', () => {
- expect(findInput().attributes('disabled')).toBe('true');
- });
- });
-});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js
new file mode 100644
index 00000000000..86f14961690
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js
@@ -0,0 +1,108 @@
+import { GlSprintf, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/packages_and_registries/settings/group/components/exceptions_input.vue';
+
+import { DUPLICATES_SETTING_EXCEPTION_TITLE } from '~/packages_and_registries/settings/group/constants';
+
+describe('Exceptions Input', () => {
+ let wrapper;
+
+ const defaultProps = {
+ duplicatesAllowed: false,
+ duplicateExceptionRegex: 'foo',
+ id: 'maven-duplicated-settings-regex-input',
+ name: 'exceptionModel',
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findInputGroup = () => wrapper.findComponent(GlFormGroup);
+ const findInput = () => wrapper.findComponent(GlFormInput);
+
+ it('shows a form group with an input field', () => {
+ mountComponent();
+
+ expect(findInputGroup().exists()).toBe(true);
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ 'label-for': defaultProps.id,
+ label: DUPLICATES_SETTING_EXCEPTION_TITLE,
+ 'label-sr-only': '',
+ });
+ });
+
+ it('shows an input field', () => {
+ mountComponent();
+
+ expect(findInput().exists()).toBe(true);
+
+ expect(findInput().attributes()).toMatchObject({
+ id: 'maven-duplicated-settings-regex-input',
+ value: defaultProps.duplicateExceptionRegex,
+ });
+ });
+
+ it('input change event emits an update event', () => {
+ mountComponent();
+
+ findInput().vm.$emit('change', 'bar');
+
+ expect(wrapper.emitted('update')).toStrictEqual([[{ [defaultProps.name]: 'bar' }]]);
+ });
+
+ describe('valid state', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('form group has correct props', () => {
+ expect(findInputGroup().attributes('input-feedback')).toBeUndefined();
+ });
+
+ it('form input has correct props', () => {
+ expect(findInput().attributes('state')).toBe('true');
+ });
+ });
+
+ describe('invalid state', () => {
+ const propsWithError = {
+ ...defaultProps,
+ duplicateExceptionRegexError: 'some error string',
+ };
+
+ beforeEach(() => {
+ mountComponent(propsWithError);
+ });
+
+ it('form group has correct props', () => {
+ expect(findInputGroup().attributes('invalid-feedback')).toBe(
+ propsWithError.duplicateExceptionRegexError,
+ );
+ });
+
+ it('form input has correct props', () => {
+ expect(findInput().attributes('state')).toBeUndefined();
+ });
+ });
+
+ describe('loading', () => {
+ beforeEach(() => {
+ mountComponent({ ...defaultProps, loading: true });
+ });
+
+ it('disables the form input', () => {
+ expect(findInput().attributes('disabled')).toBe('true');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js
deleted file mode 100644
index 4eafeedd55e..00000000000
--- a/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
-import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
-
-describe('generic_settings', () => {
- let wrapper;
-
- const mountComponent = () => {
- wrapper = shallowMount(GenericSettings, {
- scopedSlots: {
- default: '<div data-testid="default-slot">{{props.modelNames}}</div>',
- },
- });
- };
-
- const findSettingsTitle = () => wrapper.findComponent(SettingsTitles);
- const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('title component', () => {
- it('has a title component', () => {
- mountComponent();
-
- expect(findSettingsTitle().exists()).toBe(true);
- });
-
- it('passes the correct props', () => {
- mountComponent();
-
- expect(findSettingsTitle().props()).toMatchObject({
- title: 'Generic',
- subTitle: 'Settings for Generic packages',
- });
- });
- });
-
- describe('default slot', () => {
- it('accept a default slots', () => {
- mountComponent();
-
- expect(findDefaultSlot().exists()).toBe(true);
- });
-
- it('binds model names', () => {
- mountComponent();
-
- expect(findDefaultSlot().text()).toContain('genericDuplicatesAllowed');
- expect(findDefaultSlot().text()).toContain('genericDuplicateExceptionRegex');
- });
- });
-});
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
deleted file mode 100644
index 22644b97b43..00000000000
--- a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
-import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
-
-describe('maven_settings', () => {
- let wrapper;
-
- const mountComponent = () => {
- wrapper = shallowMount(MavenSettings, {
- scopedSlots: {
- default: '<div data-testid="default-slot">{{props.modelNames}}</div>',
- },
- });
- };
-
- const findSettingsTitle = () => wrapper.findComponent(SettingsTitles);
- const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('title component', () => {
- it('has a title component', () => {
- mountComponent();
-
- expect(findSettingsTitle().exists()).toBe(true);
- });
-
- it('passes the correct props', () => {
- mountComponent();
-
- expect(findSettingsTitle().props()).toMatchObject({
- title: 'Maven',
- subTitle: 'Settings for Maven packages',
- });
- });
- });
-
- describe('default slot', () => {
- it('accept a default slots', () => {
- mountComponent();
-
- expect(findDefaultSlot().exists()).toBe(true);
- });
-
- it('binds model names', () => {
- mountComponent();
-
- expect(findDefaultSlot().text()).toContain('mavenDuplicatesAllowed');
- expect(findDefaultSlot().text()).toContain('mavenDuplicateExceptionRegex');
- });
- });
-});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
index 274930ce668..13eba39ec8c 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
@@ -1,13 +1,13 @@
import Vue, { nextTick } from 'vue';
+import { GlToggle } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
-import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
+import ExceptionsInput from '~/packages_and_registries/settings/group/components/exceptions_input.vue';
import component from '~/packages_and_registries/settings/group/components/packages_settings.vue';
-import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import {
+ DUPLICATES_TOGGLE_LABEL,
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
} from '~/packages_and_registries/settings/group/constants';
@@ -35,6 +35,7 @@ describe('Packages Settings', () => {
};
const mountComponent = ({
+ mountFn = shallowMountExtended,
mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()),
} = {}) => {
Vue.use(VueApollo);
@@ -43,7 +44,7 @@ describe('Packages Settings', () => {
apolloProvider = createMockApollo(requestHandlers);
- wrapper = shallowMountExtended(component, {
+ wrapper = mountFn(component, {
apolloProvider,
provide: defaultProvide,
propsData: {
@@ -51,8 +52,6 @@ describe('Packages Settings', () => {
},
stubs: {
SettingsBlock,
- MavenSettings,
- GenericSettings,
},
});
};
@@ -63,11 +62,15 @@ describe('Packages Settings', () => {
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const findDescription = () => wrapper.findByTestId('description');
- const findMavenSettings = () => wrapper.findComponent(MavenSettings);
- const findMavenDuplicatedSettings = () => findMavenSettings().findComponent(DuplicatesSettings);
- const findGenericSettings = () => wrapper.findComponent(GenericSettings);
- const findGenericDuplicatedSettings = () =>
- findGenericSettings().findComponent(DuplicatesSettings);
+ const findMavenSettings = () => wrapper.findByTestId('maven-settings');
+ const findGenericSettings = () => wrapper.findByTestId('generic-settings');
+
+ const findMavenDuplicatedSettingsToggle = () => findMavenSettings().findComponent(GlToggle);
+ const findGenericDuplicatedSettingsToggle = () => findGenericSettings().findComponent(GlToggle);
+ const findMavenDuplicatedSettingsExceptionsInput = () =>
+ findMavenSettings().findComponent(ExceptionsInput);
+ const findGenericDuplicatedSettingsExceptionsInput = () =>
+ findGenericSettings().findComponent(ExceptionsInput);
const fillApolloCache = () => {
apolloProvider.defaultClient.cache.writeQuery({
@@ -80,7 +83,7 @@ describe('Packages Settings', () => {
};
const emitMavenSettingsUpdate = (override) => {
- findMavenDuplicatedSettings().vm.$emit('update', {
+ findGenericDuplicatedSettingsExceptionsInput().vm.$emit('update', {
mavenDuplicateExceptionRegex: ')',
...override,
});
@@ -106,27 +109,46 @@ describe('Packages Settings', () => {
describe('maven settings', () => {
it('exists', () => {
- mountComponent();
+ mountComponent({ mountFn: mountExtended });
+
+ expect(findMavenSettings().find('td').text()).toBe('Maven');
+ });
+
+ it('renders toggle', () => {
+ mountComponent({ mountFn: mountExtended });
- expect(findMavenSettings().exists()).toBe(true);
+ const { mavenDuplicatesAllowed } = packageSettings();
+
+ expect(findMavenDuplicatedSettingsToggle().exists()).toBe(true);
+
+ expect(findMavenDuplicatedSettingsToggle().props()).toMatchObject({
+ label: DUPLICATES_TOGGLE_LABEL,
+ value: mavenDuplicatesAllowed,
+ disabled: false,
+ labelPosition: 'hidden',
+ });
});
- it('assigns duplication allowness and exception props', async () => {
- mountComponent();
+ it('renders ExceptionsInput and assigns duplication allowness and exception props', () => {
+ mountComponent({ mountFn: mountExtended });
const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings();
- expect(findMavenDuplicatedSettings().props()).toMatchObject({
+ expect(findMavenDuplicatedSettingsExceptionsInput().exists()).toBe(true);
+
+ expect(findMavenDuplicatedSettingsExceptionsInput().props()).toMatchObject({
duplicatesAllowed: mavenDuplicatesAllowed,
duplicateExceptionRegex: mavenDuplicateExceptionRegex,
duplicateExceptionRegexError: '',
loading: false,
+ name: 'mavenDuplicateExceptionRegex',
+ id: 'maven-duplicated-settings-regex-input',
});
});
it('on update event calls the mutation', () => {
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
- mountComponent({ mutationResolver });
+ mountComponent({ mountFn: mountExtended, mutationResolver });
fillApolloCache();
@@ -140,31 +162,47 @@ describe('Packages Settings', () => {
describe('generic settings', () => {
it('exists', () => {
- mountComponent();
+ mountComponent({ mountFn: mountExtended });
- expect(findGenericSettings().exists()).toBe(true);
+ expect(findGenericSettings().find('td').text()).toBe('Generic');
});
- it('assigns duplication allowness and exception props', async () => {
- mountComponent();
+ it('renders toggle', () => {
+ mountComponent({ mountFn: mountExtended });
+
+ const { genericDuplicatesAllowed } = packageSettings();
+
+ expect(findGenericDuplicatedSettingsToggle().exists()).toBe(true);
+ expect(findGenericDuplicatedSettingsToggle().props()).toMatchObject({
+ label: DUPLICATES_TOGGLE_LABEL,
+ value: genericDuplicatesAllowed,
+ disabled: false,
+ labelPosition: 'hidden',
+ });
+ });
+
+ it('renders ExceptionsInput and assigns duplication allowness and exception props', async () => {
+ mountComponent({ mountFn: mountExtended });
const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings();
- expect(findGenericDuplicatedSettings().props()).toMatchObject({
+ expect(findGenericDuplicatedSettingsExceptionsInput().props()).toMatchObject({
duplicatesAllowed: genericDuplicatesAllowed,
duplicateExceptionRegex: genericDuplicateExceptionRegex,
duplicateExceptionRegexError: '',
loading: false,
+ name: 'genericDuplicateExceptionRegex',
+ id: 'generic-duplicated-settings-regex-input',
});
});
it('on update event calls the mutation', async () => {
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
- mountComponent({ mutationResolver });
+ mountComponent({ mountFn: mountExtended, mutationResolver });
fillApolloCache();
- findMavenDuplicatedSettings().vm.$emit('update', {
+ findGenericDuplicatedSettingsExceptionsInput().vm.$emit('update', {
genericDuplicateExceptionRegex: ')',
});
@@ -176,9 +214,11 @@ describe('Packages Settings', () => {
describe('settings update', () => {
describe('success state', () => {
- it('emits a success event', async () => {
- mountComponent();
+ beforeEach(() => {
+ mountComponent({ mountFn: mountExtended });
+ });
+ it('emits a success event', async () => {
fillApolloCache();
emitMavenSettingsUpdate();
@@ -189,11 +229,12 @@ describe('Packages Settings', () => {
it('has an optimistic response', () => {
const mavenDuplicateExceptionRegex = 'latest[main]something';
- mountComponent();
fillApolloCache();
- expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe('');
+ expect(
+ findGenericDuplicatedSettingsExceptionsInput().props('duplicateExceptionRegex'),
+ ).toBe('');
emitMavenSettingsUpdate({ mavenDuplicateExceptionRegex });
@@ -209,7 +250,7 @@ describe('Packages Settings', () => {
// 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 });
+ mountComponent({ mountFn: mountExtended, mutationResolver });
fillApolloCache();
@@ -218,9 +259,9 @@ describe('Packages Settings', () => {
await waitForPromises();
// errors are bound to the component
- expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe(
- groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message,
- );
+ expect(
+ findMavenDuplicatedSettingsExceptionsInput().props('duplicateExceptionRegexError'),
+ ).toBe(groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message);
// general error message is shown
@@ -231,7 +272,9 @@ describe('Packages Settings', () => {
await nextTick();
// errors are reset on mutation call
- expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe('');
+ expect(
+ findMavenDuplicatedSettingsExceptionsInput().props('duplicateExceptionRegexError'),
+ ).toBe('');
});
it.each`
@@ -239,7 +282,7 @@ describe('Packages Settings', () => {
${'local'} | ${jest.fn().mockResolvedValue(groupPackageSettingsMutationMock({ errors: ['foo'] }))}
${'network'} | ${jest.fn().mockRejectedValue()}
`('mutation payload with $type error', async ({ mutationResolver }) => {
- mountComponent({ mutationResolver });
+ mountComponent({ mountFn: mountExtended, mutationResolver });
fillApolloCache();
emitMavenSettingsUpdate();
diff --git a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
deleted file mode 100644
index fcfad4b42b8..00000000000
--- a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
-
-describe('settings_titles', () => {
- let wrapper;
-
- const defaultProps = {
- title: 'foo',
- subTitle: 'bar',
- };
-
- const mountComponent = (propsData = defaultProps) => {
- wrapper = shallowMount(SettingsTitles, {
- propsData,
- });
- };
-
- const findSubTitle = () => wrapper.find('p');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders properly', () => {
- mountComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('does not render the subtitle paragraph when no subtitle is passed', () => {
- mountComponent({ title: defaultProps.title });
-
- expect(findSubTitle().exists()).toBe(false);
- });
-});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js
new file mode 100644
index 00000000000..8b60f31512b
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js
@@ -0,0 +1,164 @@
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import component from '~/packages_and_registries/settings/project/components/cleanup_image_tags.vue';
+import ContainerExpirationPolicyForm from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue';
+import {
+ CONTAINER_CLEANUP_POLICY_TITLE,
+ CONTAINER_CLEANUP_POLICY_DESCRIPTION,
+ FETCH_SETTINGS_ERROR_MESSAGE,
+ UNAVAILABLE_FEATURE_INTRO_TEXT,
+ UNAVAILABLE_USER_FEATURE_TEXT,
+} from '~/packages_and_registries/settings/project/constants';
+import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
+
+import {
+ expirationPolicyPayload,
+ emptyExpirationPolicyPayload,
+ containerExpirationPolicyData,
+} from '../mock_data';
+
+describe('Cleanup image tags project settings', () => {
+ let wrapper;
+ let fakeApollo;
+
+ const defaultProvidedValues = {
+ projectPath: 'path',
+ isAdmin: false,
+ adminSettingsPath: 'settingsPath',
+ enableHistoricEntries: false,
+ helpPagePath: 'helpPagePath',
+ showCleanupPolicyLink: false,
+ };
+
+ const findFormComponent = () => wrapper.findComponent(ContainerExpirationPolicyForm);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findTitle = () => wrapper.findByTestId('title');
+ const findDescription = () => wrapper.findByTestId('description');
+
+ const mountComponent = (provide = defaultProvidedValues, config) => {
+ wrapper = shallowMountExtended(component, {
+ stubs: {
+ GlSprintf,
+ },
+ provide,
+ ...config,
+ });
+ };
+
+ const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => {
+ Vue.use(VueApollo);
+
+ const requestHandlers = [[expirationPolicyQuery, resolver]];
+
+ fakeApollo = createMockApollo(requestHandlers);
+ mountComponent(provide, {
+ apolloProvider: fakeApollo,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('isEdited status', () => {
+ it.each`
+ description | apiResponse | workingCopy | result
+ ${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false}
+ ${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true}
+ ${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false}
+ ${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true}
+ ${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true}
+ `('$description', async ({ apiResponse, workingCopy, result }) => {
+ mountComponentWithApollo({
+ provide: { ...defaultProvidedValues, enableHistoricEntries: true },
+ resolver: jest.fn().mockResolvedValue(apiResponse),
+ });
+ await waitForPromises();
+
+ findFormComponent().vm.$emit('input', workingCopy);
+
+ await waitForPromises();
+
+ expect(findFormComponent().props('isEdited')).toBe(result);
+ });
+ });
+
+ it('renders the setting form', async () => {
+ mountComponentWithApollo({
+ resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()),
+ });
+ await waitForPromises();
+
+ expect(findFormComponent().exists()).toBe(true);
+ expect(findTitle().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_TITLE);
+ expect(findDescription().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_DESCRIPTION);
+ });
+
+ describe('the form is disabled', () => {
+ it('hides the form', () => {
+ mountComponent();
+
+ expect(findFormComponent().exists()).toBe(false);
+ });
+
+ it('shows an alert', () => {
+ mountComponent();
+
+ const text = findAlert().text();
+ expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT);
+ expect(text).toContain(UNAVAILABLE_USER_FEATURE_TEXT);
+ });
+
+ describe('an admin is visiting the page', () => {
+ it('shows the admin part of the alert message', () => {
+ mountComponent({ ...defaultProvidedValues, isAdmin: true });
+
+ const sprintf = findAlert().findComponent(GlSprintf);
+ expect(sprintf.text()).toBe('administration settings');
+ expect(sprintf.findComponent(GlLink).attributes('href')).toBe(
+ defaultProvidedValues.adminSettingsPath,
+ );
+ });
+ });
+ });
+
+ describe('fetchSettingsError', () => {
+ beforeEach(async () => {
+ mountComponentWithApollo({
+ resolver: jest.fn().mockRejectedValue(new Error('GraphQL error')),
+ });
+ await waitForPromises();
+ });
+
+ it('hides the form', () => {
+ expect(findFormComponent().exists()).toBe(false);
+ });
+
+ it('shows an alert', () => {
+ expect(findAlert().html()).toContain(FETCH_SETTINGS_ERROR_MESSAGE);
+ });
+ });
+
+ describe('empty API response', () => {
+ it.each`
+ enableHistoricEntries | isShown
+ ${true} | ${true}
+ ${false} | ${false}
+ `('is $isShown that the form is shown', async ({ enableHistoricEntries, isShown }) => {
+ mountComponentWithApollo({
+ provide: {
+ ...defaultProvidedValues,
+ enableHistoricEntries,
+ },
+ resolver: jest.fn().mockResolvedValue(emptyExpirationPolicyPayload()),
+ });
+ await waitForPromises();
+
+ expect(findFormComponent().exists()).toBe(isShown);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
index ca44e77e694..8e08864bdb8 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
@@ -2,13 +2,11 @@ import { shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { GlCard, GlLoadingIcon } from 'jest/packages_and_registries/shared/stubs';
import component from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue';
-import {
- UPDATE_SETTINGS_ERROR_MESSAGE,
- UPDATE_SETTINGS_SUCCESS_MESSAGE,
-} from '~/packages_and_registries/settings/project/constants';
+import { UPDATE_SETTINGS_ERROR_MESSAGE } from '~/packages_and_registries/settings/project/constants';
import updateContainerExpirationPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_container_expiration_policy.mutation.graphql';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
import Tracking from '~/tracking';
@@ -20,6 +18,7 @@ describe('Container Expiration Policy Settings Form', () => {
const defaultProvidedValues = {
projectPath: 'path',
+ projectSettingsPath: 'settings-path',
};
const {
@@ -36,7 +35,7 @@ describe('Container Expiration Policy Settings Form', () => {
label: 'docker_container_retention_and_expiration_policies',
};
- const findForm = () => wrapper.find({ ref: 'form-element' });
+ const findForm = () => wrapper.find('form');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"');
const findSaveButton = () => wrapper.find('[data-testid="save-button"');
@@ -208,7 +207,9 @@ describe('Container Expiration Policy Settings Form', () => {
});
it('validation event updates buttons disabled state', async () => {
- mountComponent();
+ mountComponent({
+ props: { ...defaultProps, isEdited: true },
+ });
expect(findSaveButton().props('disabled')).toBe(false);
@@ -229,52 +230,22 @@ describe('Container Expiration Policy Settings Form', () => {
});
describe('form', () => {
- describe('form reset event', () => {
- it('calls the appropriate function', () => {
- mountComponent();
-
- findForm().trigger('reset');
-
- expect(wrapper.emitted('reset')).toEqual([[]]);
- });
-
- it('tracks the reset event', () => {
- mountComponent();
-
- findForm().trigger('reset');
-
- expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload);
- });
-
- it('resets the errors objects', async () => {
- mountComponent({
- data: { apiErrors: { nameRegex: 'bar' }, localErrors: { nameRegexKeep: false } },
- });
-
- findForm().trigger('reset');
-
- await nextTick();
+ describe('form submit event', () => {
+ useMockLocationHelper();
- expect(findKeepRegexInput().props('error')).toBe('');
- expect(findRemoveRegexInput().props('error')).toBe('');
- expect(findSaveButton().props('disabled')).toBe(false);
- });
- });
-
- describe('form submit event ', () => {
it('save has type submit', () => {
mountComponent();
expect(findSaveButton().attributes('type')).toBe('submit');
});
- it('dispatches the correct apollo mutation', () => {
+ it('dispatches the correct apollo mutation', async () => {
const mutationResolver = jest.fn().mockResolvedValue(expirationPolicyMutationPayload());
mountComponentWithApollo({
mutationResolver,
});
- findForm().trigger('submit');
+ await submitForm();
expect(mutationResolver).toHaveBeenCalled();
});
@@ -286,9 +257,7 @@ describe('Container Expiration Policy Settings Form', () => {
queryPayload: expirationPolicyPayload({ keepN: null, cadence: null, olderThan: null }),
});
- await waitForPromises();
-
- findForm().trigger('submit');
+ await submitForm();
expect(mutationResolver).toHaveBeenCalledWith({
input: {
@@ -303,24 +272,26 @@ describe('Container Expiration Policy Settings Form', () => {
});
});
- it('tracks the submit event', () => {
+ it('tracks the submit event', async () => {
mountComponentWithApollo({
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
});
- findForm().trigger('submit');
+ await submitForm();
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload);
});
- it('show a success toast when submit succeed', async () => {
+ it('redirects to package and registry project settings page when submitted successfully', async () => {
mountComponentWithApollo({
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
});
await submitForm();
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE);
+ expect(window.location.href.endsWith('settings-path?showSetupSuccessAlert=true')).toBe(
+ true,
+ );
});
describe('when submit fails', () => {
@@ -348,6 +319,7 @@ describe('Container Expiration Policy Settings Form', () => {
await submitForm();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
+ expect(window.location.href).toBeUndefined();
});
it('parses the error messages', async () => {
@@ -375,24 +347,24 @@ describe('Container Expiration Policy Settings Form', () => {
describe('form actions', () => {
describe('cancel button', () => {
- it('has type reset', () => {
+ it('links to project package and registry settings path', () => {
mountComponent();
- expect(findCancelButton().attributes('type')).toBe('reset');
+ expect(findCancelButton().attributes('href')).toBe(
+ defaultProvidedValues.projectSettingsPath,
+ );
});
it.each`
- isLoading | isEdited | mutationLoading
- ${true} | ${true} | ${true}
- ${false} | ${true} | ${true}
- ${false} | ${false} | ${true}
- ${true} | ${false} | ${false}
- ${false} | ${false} | ${false}
+ isLoading | mutationLoading
+ ${true} | ${true}
+ ${false} | ${true}
+ ${true} | ${false}
`(
- 'when isLoading is $isLoading, isEdited is $isEdited and mutationLoading is $mutationLoading is disabled',
- ({ isEdited, isLoading, mutationLoading }) => {
+ 'is disabled when isLoading is $isLoading and mutationLoading is $mutationLoading',
+ ({ isLoading, mutationLoading }) => {
mountComponent({
- props: { ...defaultProps, isEdited, isLoading },
+ props: { ...defaultProps, isLoading },
data: { mutationLoading },
});
@@ -409,18 +381,19 @@ describe('Container Expiration Policy Settings Form', () => {
});
it.each`
- isLoading | localErrors | mutationLoading
- ${true} | ${{}} | ${true}
- ${true} | ${{}} | ${false}
- ${false} | ${{}} | ${true}
- ${false} | ${{ foo: false }} | ${true}
- ${true} | ${{ foo: false }} | ${false}
- ${false} | ${{ foo: false }} | ${false}
+ isLoading | isEdited | localErrors | mutationLoading
+ ${true} | ${false} | ${{}} | ${true}
+ ${true} | ${false} | ${{}} | ${false}
+ ${false} | ${false} | ${{}} | ${true}
+ ${false} | ${false} | ${{}} | ${false}
+ ${false} | ${false} | ${{ foo: false }} | ${true}
+ ${true} | ${false} | ${{ foo: false }} | ${false}
+ ${false} | ${false} | ${{ foo: false }} | ${false}
`(
- 'when isLoading is $isLoading, localErrors is $localErrors and mutationLoading is $mutationLoading is disabled',
- ({ localErrors, isLoading, mutationLoading }) => {
+ 'is disabled when isLoading is $isLoading, isEdited is $isEdited, localErrors is $localErrors and mutationLoading is $mutationLoading',
+ ({ localErrors, isEdited, isLoading, mutationLoading }) => {
mountComponent({
- props: { ...defaultProps, isLoading },
+ props: { ...defaultProps, isEdited, isLoading },
data: { mutationLoading, localErrors },
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
index d83c717da6a..35baeaeac61 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
@@ -1,12 +1,14 @@
-import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlSprintf, GlLink, GlCard } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import component from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue';
-import ContainerExpirationPolicyForm from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue';
import {
+ CONTAINER_CLEANUP_POLICY_EDIT_RULES,
+ CONTAINER_CLEANUP_POLICY_SET_RULES,
+ CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION,
FETCH_SETTINGS_ERROR_MESSAGE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
@@ -14,11 +16,7 @@ import {
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
-import {
- expirationPolicyPayload,
- emptyExpirationPolicyPayload,
- containerExpirationPolicyData,
-} from '../mock_data';
+import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data';
describe('Container expiration policy project settings', () => {
let wrapper;
@@ -28,17 +26,19 @@ describe('Container expiration policy project settings', () => {
projectPath: 'path',
isAdmin: false,
adminSettingsPath: 'settingsPath',
+ cleanupSettingsPath: 'cleanupSettingsPath',
enableHistoricEntries: false,
helpPagePath: 'helpPagePath',
- showCleanupPolicyLink: false,
};
- const findFormComponent = () => wrapper.find(ContainerExpirationPolicyForm);
- const findAlert = () => wrapper.find(GlAlert);
- const findSettingsBlock = () => wrapper.find(SettingsBlock);
+ const findFormComponent = () => wrapper.findComponent(GlCard);
+ const findDescription = () => wrapper.findByTestId('description');
+ const findButton = () => wrapper.findByTestId('rules-button');
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const mountComponent = (provide = defaultProvidedValues, config) => {
- wrapper = shallowMount(component, {
+ wrapper = shallowMountExtended(component, {
stubs: {
GlSprintf,
SettingsBlock,
@@ -63,37 +63,19 @@ describe('Container expiration policy project settings', () => {
wrapper.destroy();
});
- describe('isEdited status', () => {
- it.each`
- description | apiResponse | workingCopy | result
- ${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false}
- ${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true}
- ${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false}
- ${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true}
- ${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true}
- `('$description', async ({ apiResponse, workingCopy, result }) => {
- mountComponentWithApollo({
- provide: { ...defaultProvidedValues, enableHistoricEntries: true },
- resolver: jest.fn().mockResolvedValue(apiResponse),
- });
- await waitForPromises();
-
- findFormComponent().vm.$emit('input', workingCopy);
-
- await waitForPromises();
-
- expect(findFormComponent().props('isEdited')).toBe(result);
- });
- });
-
it('renders the setting form', async () => {
mountComponentWithApollo({
resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()),
});
await waitForPromises();
- expect(findFormComponent().exists()).toBe(true);
expect(findSettingsBlock().exists()).toBe(true);
+ expect(findFormComponent().exists()).toBe(true);
+ expect(findDescription().text()).toMatchInterpolatedText(
+ CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION,
+ );
+ expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_EDIT_RULES);
+ expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath);
});
describe('the form is disabled', () => {
@@ -115,9 +97,9 @@ describe('Container expiration policy project settings', () => {
it('shows the admin part of the alert message', () => {
mountComponent({ ...defaultProvidedValues, isAdmin: true });
- const sprintf = findAlert().find(GlSprintf);
+ const sprintf = findAlert().findComponent(GlSprintf);
expect(sprintf.text()).toBe('administration settings');
- expect(sprintf.find(GlLink).attributes('href')).toBe(
+ expect(sprintf.findComponent(GlLink).attributes('href')).toBe(
defaultProvidedValues.adminSettingsPath,
);
});
@@ -157,6 +139,10 @@ describe('Container expiration policy project settings', () => {
await waitForPromises();
expect(findFormComponent().exists()).toBe(isShown);
+ if (isShown) {
+ expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_SET_RULES);
+ expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath);
+ }
});
});
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js
index 8b99ac6b06c..ae41fdf65e0 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js
@@ -14,8 +14,8 @@ describe('ExpirationDropdown', () => {
],
};
- const findFormSelect = () => wrapper.find(GlFormSelect);
- const findFormGroup = () => wrapper.find(GlFormGroup);
+ const findFormSelect = () => wrapper.findComponent(GlFormSelect);
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findDescription = () => wrapper.find('[data-testid="description"]');
const findOptions = () => wrapper.findAll('[data-testid="option"]');
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js
index 6b681924fcf..1cea0704154 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js
@@ -16,11 +16,11 @@ describe('ExpirationInput', () => {
const tagsRegexHelpPagePath = 'fooPath';
- const findInput = () => wrapper.find(GlFormInput);
- const findFormGroup = () => wrapper.find(GlFormGroup);
+ const findInput = () => wrapper.findComponent(GlFormInput);
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findLabel = () => wrapper.find('[data-testid="label"]');
const findDescription = () => wrapper.find('[data-testid="description"]');
- const findDescriptionLink = () => wrapper.find(GlLink);
+ const findDescriptionLink = () => wrapper.findComponent(GlLink);
const mountComponent = (props) => {
wrapper = shallowMount(component, {
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js
index 94f7783afe7..653f2a8b40e 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js
@@ -11,8 +11,8 @@ describe('ExpirationToggle', () => {
let wrapper;
const value = 'foo';
- const findInput = () => wrapper.find(GlFormInput);
- const findFormGroup = () => wrapper.find(GlFormGroup);
+ const findInput = () => wrapper.findComponent(GlFormInput);
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const mountComponent = (propsData) => {
wrapper = shallowMount(component, {
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js
index 45039614e49..55a66cebd83 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js
@@ -10,7 +10,7 @@ import {
describe('ExpirationToggle', () => {
let wrapper;
- const findToggle = () => wrapper.find(GlToggle);
+ const findToggle = () => wrapper.findComponent(GlToggle);
const findDescription = () => wrapper.find('[data-testid="description"]');
const mountComponent = (propsData) => {
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js
index 86f45d78bae..daf0ee85fdf 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js
@@ -39,7 +39,7 @@ describe('Packages Cleanup Policy Settings Form', () => {
label: 'packages_cleanup_policies',
};
- const findForm = () => wrapper.find({ ref: 'form-element' });
+ const findForm = () => wrapper.findComponent({ ref: 'form-element' });
const findSaveButton = () => wrapper.findByTestId('save-button');
const findKeepNDuplicatedPackageFilesDropdown = () =>
wrapper.findByTestId('keep-n-duplicated-package-files-dropdown');
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
index f576bc79eae..07d13839c61 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
@@ -1,41 +1,99 @@
+import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import * as commonUtils from '~/lib/utils/common_utils';
import component from '~/packages_and_registries/settings/project/components/registry_settings_app.vue';
import ContainerExpirationPolicy from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue';
import PackagesCleanupPolicy from '~/packages_and_registries/settings/project/components/packages_cleanup_policy.vue';
+import {
+ SHOW_SETUP_SUCCESS_ALERT,
+ UPDATE_SETTINGS_SUCCESS_MESSAGE,
+} from '~/packages_and_registries/settings/project/constants';
+
+jest.mock('~/lib/utils/common_utils');
describe('Registry Settings app', () => {
let wrapper;
- const findContainerExpirationPolicy = () => wrapper.find(ContainerExpirationPolicy);
- const findPackagesCleanupPolicy = () => wrapper.find(PackagesCleanupPolicy);
+ const findContainerExpirationPolicy = () => wrapper.findComponent(ContainerExpirationPolicy);
+ const findPackagesCleanupPolicy = () => wrapper.findComponent(PackagesCleanupPolicy);
+ const findAlert = () => wrapper.findComponent(GlAlert);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
- const mountComponent = (provide) => {
+ const defaultProvide = {
+ showContainerRegistrySettings: true,
+ showPackageRegistrySettings: true,
+ };
+
+ const mountComponent = (provide = defaultProvide) => {
wrapper = shallowMount(component, {
provide,
});
};
- it.each`
- showContainerRegistrySettings | showPackageRegistrySettings
- ${true} | ${false}
- ${true} | ${true}
- ${false} | ${true}
- ${false} | ${false}
- `(
- 'container expiration policy $showContainerRegistrySettings and package cleanup policy is $showPackageRegistrySettings',
- ({ showContainerRegistrySettings, showPackageRegistrySettings }) => {
- mountComponent({
- showContainerRegistrySettings,
- showPackageRegistrySettings,
+ describe('container policy success alert handling', () => {
+ const originalLocation = window.location.href;
+ const search = `?${SHOW_SETUP_SUCCESS_ALERT}=true`;
+
+ beforeEach(() => {
+ setWindowLocation(search);
+ });
+
+ afterEach(() => {
+ setWindowLocation(originalLocation);
+ });
+
+ it(`renders alert if the query string contains ${SHOW_SETUP_SUCCESS_ALERT}`, async () => {
+ mountComponent();
+
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().props()).toMatchObject({
+ dismissible: true,
+ variant: 'success',
});
+ expect(findAlert().text()).toMatchInterpolatedText(UPDATE_SETTINGS_SUCCESS_MESSAGE);
+ });
+
+ it('calls historyReplaceState with a clean url', () => {
+ mountComponent();
+
+ expect(commonUtils.historyReplaceState).toHaveBeenCalledWith(originalLocation);
+ });
+
+ it(`does nothing if the query string does not contain ${SHOW_SETUP_SUCCESS_ALERT}`, () => {
+ setWindowLocation('?');
+ mountComponent();
- expect(findContainerExpirationPolicy().exists()).toBe(showContainerRegistrySettings);
- expect(findPackagesCleanupPolicy().exists()).toBe(showPackageRegistrySettings);
- },
- );
+ expect(findAlert().exists()).toBe(false);
+ expect(commonUtils.historyReplaceState).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('settings', () => {
+ it.each`
+ showContainerRegistrySettings | showPackageRegistrySettings
+ ${true} | ${false}
+ ${true} | ${true}
+ ${false} | ${true}
+ ${false} | ${false}
+ `(
+ 'container expiration policy $showContainerRegistrySettings and package cleanup policy is $showPackageRegistrySettings',
+ ({ showContainerRegistrySettings, showPackageRegistrySettings }) => {
+ mountComponent({
+ showContainerRegistrySettings,
+ showPackageRegistrySettings,
+ });
+
+ expect(findContainerExpirationPolicy().exists()).toBe(showContainerRegistrySettings);
+ expect(findPackagesCleanupPolicy().exists()).toBe(showPackageRegistrySettings);
+ },
+ );
+ });
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js
index 7727bf167fe..18084766db9 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js
@@ -15,15 +15,15 @@ import {
import Tracking from '~/tracking';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-import { dockerCommands } from '../../mock_data';
+import { dockerCommands } from 'jest/packages_and_registries/container_registry/explorer/mock_data';
Vue.use(Vuex);
describe('cli_commands', () => {
let wrapper;
- const findDropdownButton = () => wrapper.find(GlDropdown);
- const findCodeInstruction = () => wrapper.findAll(CodeInstruction);
+ const findDropdownButton = () => wrapper.findComponent(GlDropdown);
+ const findCodeInstruction = () => wrapper.findAllComponents(CodeInstruction);
const mountComponent = () => {
wrapper = mount(QuickstartDropdown, {
diff --git a/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js
index d6d1970cb12..a0ff6ca01b5 100644
--- a/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js
@@ -5,7 +5,7 @@ import PackageIconAndName from '~/packages_and_registries/shared/components/pack
describe('PackageIconAndName', () => {
let wrapper;
- const findIcon = () => wrapper.find(GlIcon);
+ const findIcon = () => wrapper.findComponent(GlIcon);
const mountComponent = () => {
wrapper = shallowMount(PackageIconAndName, {
diff --git a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js
index 3a52c243867..3c512cfd6ae 100644
--- a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js
+++ b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js
@@ -48,7 +48,7 @@ describe('UsageStatistics', () => {
expectEnabledservicePingFeaturesCheckBox();
});
- it('is switched to disabled when Service Ping checkbox is unchecked ', () => {
+ it('is switched to disabled when Service Ping checkbox is unchecked', () => {
servicePingCheckBox.click();
servicePingFeaturesCheckBox.click();
expectEnabledservicePingFeaturesCheckBox();
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 7a8a249cb2a..b020caa3010 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
@@ -14,7 +14,7 @@ describe('BitbucketServerStatusTable', () => {
const findReconfigureButton = () =>
wrapper
- .findAll(GlButton)
+ .findAllComponents(GlButton)
.filter((w) => w.props().variant === 'info')
.at(0);
@@ -36,7 +36,7 @@ describe('BitbucketServerStatusTable', () => {
it('renders bitbucket status table component', () => {
createComponent();
- expect(wrapper.find(BitbucketStatusTable).exists()).toBe(true);
+ expect(wrapper.findComponent(BitbucketStatusTable).exists()).toBe(true);
});
it('renders Reconfigure button', async () => {
diff --git a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js
index a850b1655f7..1790a9c9bf5 100644
--- a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js
+++ b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js
@@ -84,7 +84,7 @@ describe('BulkImportsHistoryApp', () => {
describe('general behavior', () => {
it('renders loading state when loading', () => {
createComponent();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders empty state when no data is available', async () => {
@@ -92,8 +92,8 @@ describe('BulkImportsHistoryApp', () => {
createComponent();
await axios.waitForAll();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.find(GlEmptyState).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
});
it('renders table with data when history is available', async () => {
@@ -101,7 +101,7 @@ describe('BulkImportsHistoryApp', () => {
createComponent();
await axios.waitForAll();
- const table = wrapper.find(GlTable);
+ const table = wrapper.findComponent(GlTable);
expect(table.exists()).toBe(true);
// can't use .props() or .attributes() here
expect(table.vm.$attrs.items).toHaveLength(DUMMY_RESPONSE.length);
diff --git a/spec/frontend/pages/import/history/components/import_error_details_spec.js b/spec/frontend/pages/import/history/components/import_error_details_spec.js
index 4ff3f0361cf..82a3e11186e 100644
--- a/spec/frontend/pages/import/history/components/import_error_details_spec.js
+++ b/spec/frontend/pages/import/history/components/import_error_details_spec.js
@@ -41,7 +41,7 @@ describe('ImportErrorDetails', () => {
describe('general behavior', () => {
it('renders loading state when loading', () => {
createComponent();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders import_error if it is available', async () => {
@@ -50,7 +50,7 @@ describe('ImportErrorDetails', () => {
createComponent();
await axios.waitForAll();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find('pre').text()).toBe(FAKE_IMPORT_ERROR);
});
@@ -59,7 +59,7 @@ describe('ImportErrorDetails', () => {
createComponent();
await axios.waitForAll();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find('pre').text()).toBe('No additional information provided.');
});
});
diff --git a/spec/frontend/pages/import/history/components/import_history_app_spec.js b/spec/frontend/pages/import/history/components/import_history_app_spec.js
index 0d821b114cf..5030adae2fa 100644
--- a/spec/frontend/pages/import/history/components/import_history_app_spec.js
+++ b/spec/frontend/pages/import/history/components/import_history_app_spec.js
@@ -79,7 +79,7 @@ describe('ImportHistoryApp', () => {
describe('general behavior', () => {
it('renders loading state when loading', () => {
createComponent();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders empty state when no data is available', async () => {
@@ -87,8 +87,8 @@ describe('ImportHistoryApp', () => {
createComponent();
await axios.waitForAll();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.find(GlEmptyState).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
});
it('renders table with data when history is available', async () => {
@@ -96,7 +96,7 @@ describe('ImportHistoryApp', () => {
createComponent();
await axios.waitForAll();
- const table = wrapper.find(GlTable);
+ const table = wrapper.findComponent(GlTable);
expect(table.exists()).toBe(true);
expect(table.props().items).toStrictEqual(DUMMY_RESPONSE);
});
@@ -127,7 +127,7 @@ describe('ImportHistoryApp', () => {
expect(mock.history.get.length).toBe(1);
expect(mock.history.get[0].params).toStrictEqual(expect.objectContaining({ page: NEW_PAGE }));
- expect(wrapper.find(GlTable).props().items).toStrictEqual(FAKE_NEXT_PAGE_REPLY);
+ expect(wrapper.findComponent(GlTable).props().items).toStrictEqual(FAKE_NEXT_PAGE_REPLY);
});
});
diff --git a/spec/frontend/pages/profiles/show/emoji_menu_spec.js b/spec/frontend/pages/profiles/show/emoji_menu_spec.js
deleted file mode 100644
index fa6e7e51a60..00000000000
--- a/spec/frontend/pages/profiles/show/emoji_menu_spec.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import $ from 'jquery';
-import { TEST_HOST } from 'helpers/test_constants';
-import axios from '~/lib/utils/axios_utils';
-import EmojiMenu from '~/pages/profiles/show/emoji_menu';
-
-describe('EmojiMenu', () => {
- const dummyEmojiTag = '<dummy></tag>';
- const dummyToggleButtonSelector = '.toggle-button-selector';
- const dummyMenuClass = 'dummy-menu-class';
-
- let emojiMenu;
- let dummySelectEmojiCallback;
- let dummyEmojiList;
-
- beforeEach(() => {
- dummySelectEmojiCallback = jest.fn().mockName('dummySelectEmojiCallback');
- dummyEmojiList = {
- glEmojiTag() {
- return dummyEmojiTag;
- },
- normalizeEmojiName(emoji) {
- return emoji;
- },
- isEmojiNameValid() {
- return true;
- },
- getEmojiCategoryMap() {
- return { dummyCategory: [] };
- },
- };
-
- emojiMenu = new EmojiMenu(
- dummyEmojiList,
- dummyToggleButtonSelector,
- dummyMenuClass,
- dummySelectEmojiCallback,
- );
- });
-
- afterEach(() => {
- emojiMenu.destroy();
- });
-
- describe('addAward', () => {
- const dummyAwardUrl = `${TEST_HOST}/award/url`;
- const dummyEmoji = 'tropical_fish';
- const dummyVotesBlock = () => $('<div />');
-
- it('calls selectEmojiCallback', async () => {
- expect(dummySelectEmojiCallback).not.toHaveBeenCalled();
-
- await emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false);
- expect(dummySelectEmojiCallback).toHaveBeenCalledWith(dummyEmoji, dummyEmojiTag);
- });
-
- it('does not make an axios request', async () => {
- jest.spyOn(axios, 'request').mockReturnValue();
-
- await emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false);
- expect(axios.request).not.toHaveBeenCalled();
- });
- });
-
- describe('bindEvents', () => {
- beforeEach(() => {
- jest.spyOn(emojiMenu, 'registerEventListener').mockReturnValue();
- });
-
- it('binds event listeners to custom toggle button', () => {
- emojiMenu.bindEvents();
-
- expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
- 'one',
- expect.anything(),
- 'mouseenter focus',
- dummyToggleButtonSelector,
- 'mouseenter focus',
- expect.anything(),
- );
-
- expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
- 'on',
- expect.anything(),
- 'click',
- dummyToggleButtonSelector,
- expect.anything(),
- );
- });
-
- it('binds event listeners to custom menu class', () => {
- emojiMenu.bindEvents();
-
- expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
- 'on',
- expect.anything(),
- 'click',
- `.js-awards-block .js-emoji-btn, .${dummyMenuClass} .js-emoji-btn`,
- expect.anything(),
- );
- });
- });
-
- describe('createEmojiMenu', () => {
- it('renders the menu with custom menu class', () => {
- const menuElement = () =>
- document.body.querySelector(`.emoji-menu.${dummyMenuClass} .emoji-menu-content`);
-
- expect(menuElement()).toBe(null);
-
- emojiMenu.createEmojiMenu();
-
- expect(menuElement()).not.toBe(null);
- });
- });
-});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
index 2a0fde45384..f221a90da61 100644
--- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
@@ -4,11 +4,14 @@ import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import { kebabCase } from 'lodash';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import createFlash from '~/flash';
-import httpStatus from '~/lib/utils/http_status';
import * as urlUtility from '~/lib/utils/url_utility';
import ForkForm from '~/pages/projects/forks/new/components/fork_form.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql';
+import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue';
jest.mock('~/flash');
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
@@ -16,6 +19,7 @@ jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
describe('ForkForm component', () => {
let wrapper;
let axiosMock;
+ let mockQueryResponse;
const PROJECT_VISIBILITY_TYPE = {
private:
@@ -24,26 +28,11 @@ describe('ForkForm component', () => {
public: 'Public The project can be accessed without any authentication.',
};
- const GON_GITLAB_URL = 'https://gitlab.com';
const GON_API_VERSION = 'v7';
- const MOCK_NAMESPACES_RESPONSE = [
- {
- name: 'one',
- full_name: 'one-group/one',
- id: 1,
- },
- {
- name: 'two',
- full_name: 'two-group/two',
- id: 2,
- },
- ];
-
const DEFAULT_PROVIDE = {
newGroupPath: 'some/groups/path',
visibilityHelpPath: 'some/visibility/help/path',
- endpoint: '/some/project-full-path/-/forks/new.json',
projectFullPath: '/some/project-full-path',
projectId: '10',
projectName: 'Project Name',
@@ -53,12 +42,44 @@ describe('ForkForm component', () => {
restrictedVisibilityLevels: [],
};
- const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => {
- axiosMock.onGet(DEFAULT_PROVIDE.endpoint).replyOnce(statusCode, data);
- };
+ Vue.use(VueApollo);
const createComponentFactory = (mountFn) => (provide = {}, data = {}) => {
+ const queryResponse = {
+ project: {
+ id: 'gid://gitlab/Project/1',
+ forkTargets: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Group/21',
+ fullPath: 'flightjs',
+ name: 'Flight JS',
+ visibility: 'public',
+ },
+ {
+ id: 'gid://gitlab/Namespace/4',
+ fullPath: 'root',
+ name: 'Administrator',
+ visibility: 'public',
+ },
+ ],
+ },
+ },
+ };
+
+ mockQueryResponse = jest.fn().mockResolvedValue({ data: queryResponse });
+ const requestHandlers = [[searchQuery, mockQueryResponse]];
+ const apolloProvider = createMockApollo(requestHandlers);
+
+ apolloProvider.clients.defaultClient.cache.writeQuery({
+ query: searchQuery,
+ data: {
+ ...queryResponse,
+ },
+ });
+
wrapper = mountFn(ForkForm, {
+ apolloProvider,
provide: {
...DEFAULT_PROVIDE,
...provide,
@@ -83,7 +104,6 @@ describe('ForkForm component', () => {
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
window.gon = {
- gitlab_url: GON_GITLAB_URL,
api_version: GON_API_VERSION,
};
});
@@ -93,12 +113,11 @@ describe('ForkForm component', () => {
axiosMock.restore();
});
- const findFormSelectOptions = () => wrapper.find('select[name="namespace"]').findAll('option');
const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]');
const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]');
const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]');
const findForkNameInput = () => wrapper.find('[data-testid="fork-name-input"]');
- const findForkUrlInput = () => wrapper.find('[data-testid="fork-url-input"]');
+ const findForkUrlInput = () => wrapper.findComponent(ProjectNamespace);
const findForkSlugInput = () => wrapper.find('[data-testid="fork-slug-input"]');
const findForkDescriptionTextarea = () =>
wrapper.find('[data-testid="fork-description-textarea"]');
@@ -106,7 +125,6 @@ describe('ForkForm component', () => {
wrapper.find('[data-testid="fork-visibility-radio-group"]');
it('will go to projectFullPath when click cancel button', () => {
- mockGetRequest();
createComponent();
const { projectFullPath } = DEFAULT_PROVIDE;
@@ -115,8 +133,13 @@ describe('ForkForm component', () => {
expect(cancelButton.attributes('href')).toBe(projectFullPath);
});
+ const selectedMockNamespace = { name: 'two', full_name: 'two-group/two', id: 2 };
+
+ const fillForm = () => {
+ findForkUrlInput().vm.$emit('select', selectedMockNamespace);
+ };
+
it('has input with csrf token', () => {
- mockGetRequest();
createComponent();
expect(wrapper.find('input[name="authenticity_token"]').attributes('value')).toBe(
@@ -125,7 +148,6 @@ describe('ForkForm component', () => {
});
it('pre-populate form from project props', () => {
- mockGetRequest();
createComponent();
expect(findForkNameInput().attributes('value')).toBe(DEFAULT_PROVIDE.projectName);
@@ -135,75 +157,19 @@ describe('ForkForm component', () => {
);
});
- it('sets project URL prepend text with gon.gitlab_url', () => {
- mockGetRequest();
- createComponent();
-
- expect(wrapper.find(GlFormInputGroup).text()).toContain(`${GON_GITLAB_URL}/`);
- });
-
it('will have required attribute for required fields', () => {
- mockGetRequest();
createComponent();
expect(findForkNameInput().attributes('required')).not.toBeUndefined();
- expect(findForkUrlInput().attributes('required')).not.toBeUndefined();
expect(findForkSlugInput().attributes('required')).not.toBeUndefined();
expect(findVisibilityRadioGroup().attributes('required')).not.toBeUndefined();
expect(findForkDescriptionTextarea().attributes('required')).toBeUndefined();
});
- describe('forks namespaces', () => {
- beforeEach(() => {
- mockGetRequest({ namespaces: MOCK_NAMESPACES_RESPONSE });
- createFullComponent();
- });
-
- it('make GET request from endpoint', async () => {
- await axios.waitForAll();
-
- expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROVIDE.endpoint);
- });
-
- it('generate default option', async () => {
- await axios.waitForAll();
-
- const optionsArray = findForkUrlInput().findAll('option');
-
- expect(optionsArray.at(0).text()).toBe('Select a namespace');
- });
-
- it('populate project url namespace options', async () => {
- await axios.waitForAll();
-
- const optionsArray = findForkUrlInput().findAll('option');
-
- expect(optionsArray).toHaveLength(MOCK_NAMESPACES_RESPONSE.length + 1);
- expect(optionsArray.at(1).text()).toBe(MOCK_NAMESPACES_RESPONSE[0].full_name);
- expect(optionsArray.at(2).text()).toBe(MOCK_NAMESPACES_RESPONSE[1].full_name);
- });
-
- it('set namespaces in alphabetical order', async () => {
- const namespace = {
- name: 'three',
- full_name: 'aaa/three',
- id: 3,
- };
- mockGetRequest({
- namespaces: [...MOCK_NAMESPACES_RESPONSE, namespace],
- });
- createComponent();
- await axios.waitForAll();
-
- expect(wrapper.vm.namespaces).toEqual([namespace, ...MOCK_NAMESPACES_RESPONSE]);
- });
- });
-
describe('project slug', () => {
const projectPath = 'some other project slug';
beforeEach(() => {
- mockGetRequest();
createComponent({
projectPath,
});
@@ -232,10 +198,9 @@ describe('ForkForm component', () => {
describe('visibility level', () => {
it('displays the correct description', () => {
- mockGetRequest();
createComponent();
- const formRadios = wrapper.findAll(GlFormRadio);
+ const formRadios = wrapper.findAllComponents(GlFormRadio);
Object.keys(PROJECT_VISIBILITY_TYPE).forEach((visibilityType, index) => {
expect(formRadios.at(index).text()).toBe(PROJECT_VISIBILITY_TYPE[visibilityType]);
@@ -243,10 +208,9 @@ describe('ForkForm component', () => {
});
it('displays all 3 visibility levels', () => {
- mockGetRequest();
createComponent();
- expect(wrapper.findAll(GlFormRadio)).toHaveLength(3);
+ expect(wrapper.findAllComponents(GlFormRadio)).toHaveLength(3);
});
describe('when the namespace is changed', () => {
@@ -262,16 +226,12 @@ describe('ForkForm component', () => {
},
];
- beforeEach(() => {
- mockGetRequest();
- });
-
it('resets the visibility to default "private"', async () => {
createFullComponent({ projectVisibility: 'public' }, { namespaces });
expect(wrapper.vm.form.fields.visibility.value).toBe('public');
- await findFormSelectOptions().at(1).setSelected();
+ fillForm();
await nextTick();
expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true);
@@ -280,8 +240,7 @@ describe('ForkForm component', () => {
it('sets the visibility to be null when restrictedVisibilityLevels is set', async () => {
createFullComponent({ restrictedVisibilityLevels: [10] }, { namespaces });
- await findFormSelectOptions().at(1).setSelected();
-
+ fillForm();
await nextTick();
const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i });
@@ -315,8 +274,7 @@ describe('ForkForm component', () => {
${'public'} | ${[0, 20]}
${'public'} | ${[10, 20]}
${'public'} | ${[0, 10, 20]}
- `('checks the correct radio button', async ({ project, restrictedVisibilityLevels }) => {
- mockGetRequest();
+ `('checks the correct radio button', ({ project, restrictedVisibilityLevels }) => {
createFullComponent({
projectVisibility: project,
restrictedVisibilityLevels,
@@ -357,7 +315,7 @@ describe('ForkForm component', () => {
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
`(
'sets appropriate radio button disabled state',
- async ({
+ ({
project,
namespace,
privateIsDisabled,
@@ -365,7 +323,6 @@ describe('ForkForm component', () => {
publicIsDisabled,
restrictedVisibilityLevels,
}) => {
- mockGetRequest();
createComponent(
{
projectVisibility: project,
@@ -387,11 +344,9 @@ describe('ForkForm component', () => {
const setupComponent = (fields = {}) => {
jest.spyOn(urlUtility, 'redirectTo').mockImplementation();
- mockGetRequest();
createFullComponent(
{},
{
- namespaces: MOCK_NAMESPACES_RESPONSE,
form: {
state: true,
...fields,
@@ -400,25 +355,21 @@ describe('ForkForm component', () => {
);
};
- const selectedMockNamespaceIndex = 1;
- const namespaceId = MOCK_NAMESPACES_RESPONSE[selectedMockNamespaceIndex].id;
-
- const fillForm = async () => {
- const namespaceOptions = findForkUrlInput().findAll('option');
-
- await namespaceOptions.at(selectedMockNamespaceIndex + 1).setSelected();
- };
+ beforeEach(() => {
+ setupComponent();
+ });
const submitForm = async () => {
- await fillForm();
- const form = wrapper.find(GlForm);
+ fillForm();
+ await nextTick();
+ const form = wrapper.findComponent(GlForm);
await form.trigger('submit');
await nextTick();
};
describe('with invalid form', () => {
- it('does not make POST request', async () => {
+ it('does not make POST request', () => {
jest.spyOn(axios, 'post');
setupComponent();
@@ -471,7 +422,7 @@ describe('ForkForm component', () => {
description: projectDescription,
id: projectId,
name: projectName,
- namespace_id: namespaceId,
+ namespace_id: selectedMockNamespace.id,
path: projectPath,
visibility: projectVisibility,
};
diff --git a/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js
new file mode 100644
index 00000000000..1a88aebae32
--- /dev/null
+++ b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js
@@ -0,0 +1,177 @@
+import {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlSearchBoxByType,
+ GlTruncate,
+} from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import createFlash from '~/flash';
+import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql';
+import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue';
+
+jest.mock('~/flash');
+
+describe('ProjectNamespace component', () => {
+ let wrapper;
+ let originalGon;
+
+ const data = {
+ project: {
+ __typename: 'Project',
+ id: 'gid://gitlab/Project/1',
+ forkTargets: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Group/21',
+ fullPath: 'flightjs',
+ name: 'Flight JS',
+ visibility: 'public',
+ },
+ {
+ id: 'gid://gitlab/Namespace/4',
+ fullPath: 'root',
+ name: 'Administrator',
+ visibility: 'public',
+ },
+ ],
+ },
+ },
+ };
+
+ const mockQueryResponse = jest.fn().mockResolvedValue({ data });
+
+ const emptyQueryResponse = {
+ project: {
+ __typename: 'Project',
+ id: 'gid://gitlab/Project/1',
+ forkTargets: {
+ nodes: [],
+ },
+ },
+ };
+
+ const mockQueryError = jest.fn().mockRejectedValue(new Error('Network error'));
+
+ Vue.use(VueApollo);
+
+ const gitlabUrl = 'https://gitlab.com';
+
+ const defaultProvide = {
+ projectFullPath: 'gitlab-org/project',
+ };
+
+ const mountComponent = ({
+ provide = defaultProvide,
+ queryHandler = mockQueryResponse,
+ mountFn = shallowMount,
+ } = {}) => {
+ const requestHandlers = [[searchQuery, queryHandler]];
+ const apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = mountFn(ProjectNamespace, {
+ apolloProvider,
+ provide,
+ });
+ };
+
+ const findButtonLabel = () => wrapper.findComponent(GlButton);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownText = () => wrapper.findComponent(GlTruncate);
+ const findInput = () => wrapper.findComponent(GlSearchBoxByType);
+
+ const clickDropdownItem = async () => {
+ wrapper.findComponent(GlDropdownItem).vm.$emit('click');
+ await nextTick();
+ };
+
+ const showDropdown = () => {
+ findDropdown().vm.$emit('shown');
+ };
+
+ beforeAll(() => {
+ originalGon = window.gon;
+ window.gon = { gitlab_url: gitlabUrl };
+ });
+
+ afterAll(() => {
+ window.gon = originalGon;
+ wrapper.destroy();
+ });
+
+ describe('Initial state', () => {
+ beforeEach(() => {
+ mountComponent({ mountFn: mount });
+ jest.runOnlyPendingTimers();
+ });
+
+ it('renders the root url as a label', () => {
+ expect(findButtonLabel().text()).toBe(`${gitlabUrl}/`);
+ expect(findButtonLabel().props('label')).toBe(true);
+ });
+
+ it('renders placeholder text', () => {
+ expect(findDropdownText().props('text')).toBe('Select a namespace');
+ });
+ });
+
+ describe('After user interactions', () => {
+ beforeEach(async () => {
+ mountComponent({ mountFn: mount });
+ jest.runOnlyPendingTimers();
+ await nextTick();
+ showDropdown();
+ });
+
+ it('focuses on the input when the dropdown is opened', () => {
+ const spy = jest.spyOn(findInput().vm, 'focusInput');
+ showDropdown();
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ it('displays fetched namespaces', () => {
+ const listItems = wrapper.findAll('li');
+ expect(listItems).toHaveLength(3);
+ expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Namespaces');
+ expect(listItems.at(1).text()).toBe(data.project.forkTargets.nodes[0].fullPath);
+ expect(listItems.at(2).text()).toBe(data.project.forkTargets.nodes[1].fullPath);
+ });
+
+ it('sets the selected namespace', async () => {
+ const { fullPath } = data.project.forkTargets.nodes[0];
+ await clickDropdownItem();
+ expect(findDropdownText().props('text')).toBe(fullPath);
+ });
+ });
+
+ describe('With empty query response', () => {
+ beforeEach(() => {
+ mountComponent({ queryHandler: emptyQueryResponse, mountFn: mount });
+ jest.runOnlyPendingTimers();
+ });
+
+ it('renders `No matches found`', () => {
+ expect(wrapper.find('li').text()).toBe('No matches found');
+ });
+ });
+
+ describe('With error while fetching data', () => {
+ beforeEach(async () => {
+ mountComponent({ queryHandler: mockQueryError });
+ jest.runOnlyPendingTimers();
+ await nextTick();
+ });
+
+ it('creates a flash message and captures the error', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'Something went wrong while loading data. Please refresh the page to try again.',
+ captureError: true,
+ error: expect.any(Error),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
index f272891919d..2f2edd6b025 100644
--- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js
+++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
@@ -20,9 +20,9 @@ describe('Code Coverage', () => {
const graphRef = 'master';
const graphCsvPath = 'url/';
- const findAlert = () => wrapper.find(GlAlert);
- const findAreaChart = () => wrapper.find(GlAreaChart);
- const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findAreaChart = () => wrapper.findComponent(GlAreaChart);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findAllDropdownItems().at(0);
const findSecondDropdownItem = () => findAllDropdownItems().at(1);
const findDownloadButton = () => wrapper.find('[data-testid="download-button"]');
@@ -142,7 +142,7 @@ describe('Code Coverage', () => {
});
it('renders the dropdown with all custom names as options', () => {
- expect(wrapper.find(GlDropdown).exists()).toBeDefined();
+ expect(wrapper.findComponent(GlDropdown).exists()).toBeDefined();
expect(findAllDropdownItems()).toHaveLength(codeCoverageMockData.length);
expect(findFirstDropdownItem().text()).toBe(codeCoverageMockData[0].group_name);
});
diff --git a/spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js b/spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js
new file mode 100644
index 00000000000..72077038dff
--- /dev/null
+++ b/spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js
@@ -0,0 +1,59 @@
+import { setHTMLFixture, resetHTMLFixture } from 'jest/__helpers__/fixtures';
+import initFormUpdate from '~/pages/projects/merge_requests/edit/update_form';
+
+describe('Update form state', () => {
+ const submitEvent = new Event('submit', {
+ bubbles: true,
+ cancelable: true,
+ });
+
+ const submitForm = () => document.querySelector('.merge-request-form').dispatchEvent(submitEvent);
+ const hiddenInputs = () => document.querySelectorAll('input[type="hidden"]');
+ const checkboxes = () => document.querySelectorAll('.js-form-update');
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <form class="merge-request-form">
+ <div class="form-check">
+ <input type="hidden" name="merge_request[force_remove_source_branch]" value="0" autocomplete="off">
+ <input type="checkbox" name="merge_request[force_remove_source_branch]" id="merge_request_force_remove_source_branch" value="1" class="form-check-input js-form-update">
+ </div>
+ <div class="form-check">
+ <input type="hidden" name="merge_request[squash]" value="0" autocomplete="off">
+ <input type="checkbox" name="merge_request[squash]" id="merge_request_squash" value="1" class="form-check-input js-form-update">
+ </div>
+ </form>`);
+ initFormUpdate();
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ it('at initial state', () => {
+ submitForm();
+ expect(hiddenInputs()).toHaveLength(2);
+ });
+
+ it('when one element is checked', () => {
+ checkboxes()[0].setAttribute('checked', true);
+ submitForm();
+ expect(hiddenInputs()).toHaveLength(1);
+ });
+
+ it('when all elements are checked', () => {
+ checkboxes()[0].setAttribute('checked', true);
+ checkboxes()[1].setAttribute('checked', true);
+ submitForm();
+ expect(hiddenInputs()).toHaveLength(0);
+ });
+
+ it('when checked and then unchecked', () => {
+ checkboxes()[0].setAttribute('checked', true);
+ checkboxes()[0].removeAttribute('checked');
+ checkboxes()[1].setAttribute('checked', true);
+ checkboxes()[1].removeAttribute('checked');
+ submitForm();
+ expect(hiddenInputs()).toHaveLength(2);
+ });
+});
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 ca7f70f4434..a633332ab65 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
@@ -21,7 +21,7 @@ describe('Pipeline Schedule Callout', () => {
};
const findInnerContentOfCallout = () => wrapper.find('[data-testid="innerContent"]');
- const findDismissCalloutBtn = () => wrapper.find(GlButton);
+ const findDismissCalloutBtn = () => wrapper.findComponent(GlButton);
describe(`when ${cookieKey} cookie is set`, () => {
beforeEach(async () => {
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 f908508c4b5..ed7d4ad269e 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
@@ -5,8 +5,12 @@ import settingsPanel from '~/pages/projects/shared/permissions/components/settin
import {
featureAccessLevel,
visibilityLevelDescriptions,
- visibilityOptions,
} from '~/pages/projects/shared/permissions/constants';
+import {
+ VISIBILITY_LEVEL_PRIVATE_INTEGER,
+ VISIBILITY_LEVEL_INTERNAL_INTEGER,
+ VISIBILITY_LEVEL_PUBLIC_INTEGER,
+} from '~/visibility_level/constants';
import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue';
const defaultProps = {
@@ -81,15 +85,17 @@ describe('Settings Panel', () => {
});
};
- const findLFSSettingsRow = () => wrapper.find({ ref: 'git-lfs-settings' });
+ const findLFSSettingsRow = () => wrapper.findComponent({ ref: 'git-lfs-settings' });
const findLFSSettingsMessage = () => findLFSSettingsRow().find('p');
- const findLFSFeatureToggle = () => findLFSSettingsRow().find(GlToggle);
- const findRepositoryFeatureProjectRow = () => wrapper.find({ ref: 'repository-settings' });
+ const findLFSFeatureToggle = () => findLFSSettingsRow().findComponent(GlToggle);
+ const findRepositoryFeatureProjectRow = () =>
+ wrapper.findComponent({ ref: 'repository-settings' });
const findRepositoryFeatureSetting = () =>
- findRepositoryFeatureProjectRow().find(ProjectFeatureSetting);
- const findProjectVisibilitySettings = () => wrapper.find({ ref: 'project-visibility-settings' });
- const findIssuesSettingsRow = () => wrapper.find({ ref: 'issues-settings' });
- const findAnalyticsRow = () => wrapper.find({ ref: 'analytics-settings' });
+ findRepositoryFeatureProjectRow().findComponent(ProjectFeatureSetting);
+ const findProjectVisibilitySettings = () =>
+ wrapper.findComponent({ ref: 'project-visibility-settings' });
+ const findIssuesSettingsRow = () => wrapper.findComponent({ ref: 'issues-settings' });
+ const findAnalyticsRow = () => wrapper.findComponent({ ref: 'analytics-settings' });
const findProjectVisibilityLevelInput = () => wrapper.find('[name="project[visibility_level]"]');
const findRequestAccessEnabledInput = () =>
wrapper.find('[name="project[request_access_enabled]"]');
@@ -99,35 +105,40 @@ describe('Settings Panel', () => {
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 findContainerRegistrySettings = () =>
+ wrapper.findComponent({ ref: 'container-registry-settings' });
const findContainerRegistryPublicNoteGlSprintfComponent = () =>
findContainerRegistrySettings().findComponent(GlSprintf);
const findContainerRegistryAccessLevelInput = () =>
wrapper.find('[name="project[project_feature_attributes][container_registry_access_level]"]');
- const findPackageSettings = () => wrapper.find({ ref: 'package-settings' });
+ const findPackageSettings = () => wrapper.findComponent({ ref: 'package-settings' });
const findPackageAccessLevel = () =>
wrapper.find('[data-testid="package-registry-access-level"]');
const findPackageAccessLevels = () =>
wrapper.find('[name="project[project_feature_attributes][package_registry_access_level]"]');
const findPackagesEnabledInput = () => wrapper.find('[name="project[packages_enabled]"]');
- const findPagesSettings = () => wrapper.find({ ref: 'pages-settings' });
+ const findPagesSettings = () => wrapper.findComponent({ ref: 'pages-settings' });
const findPagesAccessLevels = () =>
wrapper.find('[name="project[project_feature_attributes][pages_access_level]"]');
- const findEmailSettings = () => wrapper.find({ ref: 'email-settings' });
+ const findEmailSettings = () => wrapper.findComponent({ ref: 'email-settings' });
const findShowDefaultAwardEmojis = () =>
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
const findWarnAboutPuc = () =>
wrapper.find(
'input[name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"]',
);
- const findMetricsVisibilitySettings = () => wrapper.find({ ref: 'metrics-visibility-settings' });
+ const findMetricsVisibilitySettings = () =>
+ wrapper.findComponent({ ref: 'metrics-visibility-settings' });
const findMetricsVisibilityInput = () =>
findMetricsVisibilitySettings().findComponent(ProjectFeatureSetting);
- const findOperationsSettings = () => wrapper.find({ ref: 'operations-settings' });
+ const findOperationsSettings = () => wrapper.findComponent({ ref: 'operations-settings' });
const findOperationsVisibilityInput = () =>
findOperationsSettings().findComponent(ProjectFeatureSetting);
const findConfirmDangerButton = () => wrapper.findComponent(ConfirmDanger);
const findEnvironmentsSettings = () => wrapper.findComponent({ ref: 'environments-settings' });
+ const findFeatureFlagsSettings = () => wrapper.findComponent({ ref: 'feature-flags-settings' });
+ const findReleasesSettings = () => wrapper.findComponent({ ref: 'environments-settings' });
+ const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' });
afterEach(() => {
wrapper.destroy();
@@ -156,13 +167,13 @@ describe('Settings Panel', () => {
});
it.each`
- option | allowedOptions | disabled
- ${visibilityOptions.PRIVATE} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
- ${visibilityOptions.PRIVATE} | ${[visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${true}
- ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
- ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.PUBLIC]} | ${true}
- ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
- ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL]} | ${true}
+ option | allowedOptions | disabled
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${true}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${true}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER]} | ${true}
`(
'sets disabled to $disabled for the visibility option $option when given $allowedOptions',
({ option, allowedOptions, disabled }) => {
@@ -181,35 +192,37 @@ describe('Settings Panel', () => {
it('should set the visibility level description based upon the selected visibility level', () => {
wrapper = mountComponent({ stubs: { GlSprintf } });
- findProjectVisibilityLevelInput().setValue(visibilityOptions.INTERNAL);
+ findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_INTERNAL_INTEGER);
expect(findProjectVisibilitySettings().text()).toContain(
- visibilityLevelDescriptions[visibilityOptions.INTERNAL],
+ visibilityLevelDescriptions[VISIBILITY_LEVEL_INTERNAL_INTEGER],
);
});
it('should show the request access checkbox if the visibility level is not private', () => {
wrapper = mountComponent({
- currentSettings: { visibilityLevel: visibilityOptions.INTERNAL },
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER },
});
expect(findRequestAccessEnabledInput().exists()).toBe(true);
});
it('should not show the request access checkbox if the visibility level is private', () => {
- wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } });
+ wrapper = mountComponent({
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER },
+ });
expect(findRequestAccessEnabledInput().exists()).toBe(false);
});
it('does not require confirmation if the visibility is reduced', async () => {
wrapper = mountComponent({
- currentSettings: { visibilityLevel: visibilityOptions.INTERNAL },
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER },
});
expect(findConfirmDangerButton().exists()).toBe(false);
- await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE);
+ await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER);
expect(findConfirmDangerButton().exists()).toBe(false);
});
@@ -217,7 +230,7 @@ describe('Settings Panel', () => {
describe('showVisibilityConfirmModal=true', () => {
beforeEach(() => {
wrapper = mountComponent({
- currentSettings: { visibilityLevel: visibilityOptions.INTERNAL },
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER },
showVisibilityConfirmModal: true,
});
});
@@ -225,7 +238,7 @@ describe('Settings Panel', () => {
it('will render the confirmation dialog if the visibility is reduced', async () => {
expect(findConfirmDangerButton().exists()).toBe(false);
- await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE);
+ await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER);
expect(findConfirmDangerButton().exists()).toBe(true);
});
@@ -233,7 +246,7 @@ describe('Settings Panel', () => {
it('emits the `confirm` event when the reduce visibility warning is confirmed', async () => {
expect(wrapper.emitted('confirm')).toBeUndefined();
- await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE);
+ await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER);
await findConfirmDangerButton().vm.$emit('confirm');
expect(wrapper.emitted('confirm')).toHaveLength(1);
@@ -253,7 +266,9 @@ describe('Settings Panel', () => {
describe('Repository', () => {
it('should set the repository help text when the visibility level is set to private', () => {
- wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } });
+ wrapper = mountComponent({
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER },
+ });
expect(findRepositoryFeatureProjectRow().props('helpText')).toBe(
'View and edit files in this project.',
@@ -261,7 +276,9 @@ describe('Settings Panel', () => {
});
it('should set the repository help text with a read access warning when the visibility level is set to non-private', () => {
- wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC } });
+ wrapper = mountComponent({
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER },
+ });
expect(findRepositoryFeatureProjectRow().props('helpText')).toBe(
'View and edit files in this project. Non-project members have only read access.',
@@ -345,7 +362,7 @@ describe('Settings Panel', () => {
it('should show the container registry public note if the visibility level is public and the registry is available', () => {
wrapper = mountComponent({
currentSettings: {
- visibilityLevel: visibilityOptions.PUBLIC,
+ visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER,
containerRegistryAccessLevel: featureAccessLevel.EVERYONE,
},
registryAvailable: true,
@@ -360,7 +377,7 @@ describe('Settings Panel', () => {
it('should hide the container registry public note if the visibility level is public but the registry is private', () => {
wrapper = mountComponent({
currentSettings: {
- visibilityLevel: visibilityOptions.PUBLIC,
+ visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER,
containerRegistryAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
},
registryAvailable: true,
@@ -371,7 +388,7 @@ describe('Settings Panel', () => {
it('should hide the container registry public note if the visibility level is private and the registry is available', () => {
wrapper = mountComponent({
- currentSettings: { visibilityLevel: visibilityOptions.PRIVATE },
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER },
registryAvailable: true,
});
@@ -380,7 +397,7 @@ describe('Settings Panel', () => {
it('has label for the toggle', () => {
wrapper = mountComponent({
- currentSettings: { visibilityLevel: visibilityOptions.PUBLIC },
+ currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER },
registryAvailable: true,
});
@@ -569,10 +586,10 @@ describe('Settings Panel', () => {
});
it.each`
- visibilityLevel | output
- ${visibilityOptions.PRIVATE} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]}
- ${visibilityOptions.INTERNAL} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]}
- ${visibilityOptions.PUBLIC} | ${[[30, 'Everyone']]}
+ visibilityLevel | output
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[[30, 'Everyone']]}
`(
'renders correct options when visibilityLevel is $visibilityLevel',
async ({ visibilityLevel, output }) => {
@@ -589,23 +606,23 @@ describe('Settings Panel', () => {
);
it.each`
- initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption
- ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
- ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE}
- ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
- ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
- ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
- ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
- ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
- ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS}
- ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
- ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
- ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
- ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
- ${visibilityOptions.PUBLIC} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
- ${visibilityOptions.PUBLIC} | ${visibilityOptions.PRIVATE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS}
- ${visibilityOptions.PUBLIC} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
- ${visibilityOptions.PUBLIC} | ${visibilityOptions.INTERNAL} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE}
+ initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE}
`(
'changes option from $initialPackageRegistryOption to $expectedPackageRegistryOption when visibilityLevel changed from $initialProjectVisibilityLevel to $newProjectVisibilityLevel',
async ({
@@ -635,13 +652,13 @@ describe('Settings Panel', () => {
describe('Pages', () => {
it.each`
- visibilityLevel | pagesAccessControlForced | output
- ${visibilityOptions.PRIVATE} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]}
- ${visibilityOptions.PRIVATE} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]}
- ${visibilityOptions.INTERNAL} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]}
- ${visibilityOptions.INTERNAL} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]}
- ${visibilityOptions.PUBLIC} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]}
- ${visibilityOptions.PUBLIC} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]}
+ visibilityLevel | pagesAccessControlForced | output
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]}
+ ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]}
+ ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]}
+ ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]}
`(
'renders correct options when pagesAccessControlForced is $pagesAccessControlForced and visibilityLevel is $visibilityLevel',
async ({ visibilityLevel, pagesAccessControlForced, output }) => {
@@ -760,13 +777,13 @@ describe('Settings Panel', () => {
it('should reduce Metrics visibility level when visibility is set to private', async () => {
wrapper = mountComponent({
currentSettings: {
- visibilityLevel: visibilityOptions.PUBLIC,
+ visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER,
operationsAccessLevel: featureAccessLevel.EVERYONE,
metricsDashboardAccessLevel: featureAccessLevel.EVERYONE,
},
});
- await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE);
+ await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER);
expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.PROJECT_MEMBERS);
});
@@ -806,4 +823,78 @@ describe('Settings Panel', () => {
});
});
});
+ describe('Feature Flags', () => {
+ describe('with feature flag', () => {
+ it('should show the feature flags toggle', () => {
+ wrapper = mountComponent({
+ glFeatures: { splitOperationsVisibilityPermissions: true },
+ });
+
+ expect(findFeatureFlagsSettings().exists()).toBe(true);
+ });
+ });
+ describe('without feature flag', () => {
+ it('should not show the feature flags toggle', () => {
+ wrapper = mountComponent({});
+
+ expect(findFeatureFlagsSettings().exists()).toBe(false);
+ });
+ });
+ });
+ describe('Releases', () => {
+ describe('with feature flag', () => {
+ it('should show the releases toggle', () => {
+ wrapper = mountComponent({
+ glFeatures: { splitOperationsVisibilityPermissions: true },
+ });
+
+ expect(findReleasesSettings().exists()).toBe(true);
+ });
+ });
+ describe('without feature flag', () => {
+ it('should not show the releases toggle', () => {
+ wrapper = mountComponent({});
+
+ expect(findReleasesSettings().exists()).toBe(false);
+ });
+ });
+ });
+ describe('Monitor', () => {
+ const expectedAccessLevel = [
+ [10, 'Only Project Members'],
+ [20, 'Everyone With Access'],
+ ];
+ describe('with feature flag', () => {
+ it('shows Monitor toggle instead of Operations toggle', () => {
+ wrapper = mountComponent({
+ glFeatures: { splitOperationsVisibilityPermissions: true },
+ });
+
+ expect(findMonitorSettings().exists()).toBe(true);
+ expect(findOperationsSettings().exists()).toBe(false);
+ expect(findMonitorSettings().findComponent(ProjectFeatureSetting).props('options')).toEqual(
+ expectedAccessLevel,
+ );
+ });
+ it('when monitorAccessLevel is for project members, it is also for everyone', () => {
+ wrapper = mountComponent({
+ glFeatures: { splitOperationsVisibilityPermissions: true },
+ currentSettings: { monitorAccessLevel: featureAccessLevel.PROJECT_MEMBERS },
+ });
+
+ expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.EVERYONE);
+ });
+ });
+ describe('without feature flag', () => {
+ it('shows Operations toggle instead of Monitor toggle', () => {
+ wrapper = mountComponent({});
+
+ expect(findMonitorSettings().exists()).toBe(false);
+ expect(findOperationsSettings().exists()).toBe(true);
+ expect(
+ findOperationsSettings().findComponent(ProjectFeatureSetting).props('options'),
+ ).toEqual(expectedAccessLevel);
+ });
+ });
+ });
});
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js
index 108f816fe01..982c81b9272 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js
@@ -38,7 +38,7 @@ describe('pages/shared/wikis/components/wiki_content', () => {
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
- const findContent = () => wrapper.find('[data-testid="wiki_page_content"]');
+ const findContent = () => wrapper.find('[data-testid="wiki-page-content"]');
describe('when loading content', () => {
beforeEach(() => {
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
index 204c48f8de1..b37d2f06191 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
@@ -39,7 +39,7 @@ describe('WikiForm', () => {
const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link');
const findContentEditor = () => wrapper.findComponent(ContentEditor);
const findClassicEditor = () => wrapper.findComponent(MarkdownField);
- const findLocalStorageSync = () => wrapper.find(LocalStorageSync);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const setFormat = (value) => {
const format = findFormat();
@@ -302,19 +302,15 @@ describe('WikiForm', () => {
});
it.each`
- format | enabled | action
+ format | exists | action
${'markdown'} | ${true} | ${'displays'}
${'rdoc'} | ${false} | ${'hides'}
${'asciidoc'} | ${false} | ${'hides'}
${'org'} | ${false} | ${'hides'}
- `('$action toggle editing mode button when format is $format', async ({ format, enabled }) => {
+ `('$action toggle editing mode button when format is $format', async ({ format, exists }) => {
await setFormat(format);
- expect(findToggleEditingModeButton().exists()).toBe(enabled);
- });
-
- it('displays toggle editing mode button', () => {
- expect(findToggleEditingModeButton().exists()).toBe(true);
+ expect(findToggleEditingModeButton().exists()).toBe(exists);
});
describe('when content editor is not active', () => {
@@ -351,15 +347,8 @@ describe('WikiForm', () => {
});
describe('when content editor is active', () => {
- let mockContentEditor;
-
beforeEach(() => {
createWrapper();
- mockContentEditor = {
- getSerializedContent: jest.fn(),
- setSerializedContent: jest.fn(),
- };
-
findToggleEditingModeButton().vm.$emit('input', 'richText');
});
@@ -368,14 +357,7 @@ describe('WikiForm', () => {
});
describe('when clicking the toggle editing mode button', () => {
- const contentEditorFakeSerializedContent = 'fake content';
-
beforeEach(async () => {
- mockContentEditor.getSerializedContent.mockReturnValueOnce(
- contentEditorFakeSerializedContent,
- );
-
- findContentEditor().vm.$emit('initialized', mockContentEditor);
await findToggleEditingModeButton().vm.$emit('input', 'source');
await nextTick();
});
@@ -387,10 +369,6 @@ describe('WikiForm', () => {
it('displays the classic editor', () => {
expect(findClassicEditor().exists()).toBe(true);
});
-
- it('updates the classic editor content field', () => {
- expect(findContent().element.value).toBe(contentEditorFakeSerializedContent);
- });
});
describe('when content editor is loading', () => {
@@ -480,8 +458,14 @@ describe('WikiForm', () => {
});
describe('when wiki content is updated', () => {
+ const updatedMarkdown = 'hello **world**';
+
beforeEach(() => {
- findContentEditor().vm.$emit('change', { empty: false });
+ findContentEditor().vm.$emit('change', {
+ empty: false,
+ changed: true,
+ markdown: updatedMarkdown,
+ });
});
it('sets before unload warning', () => {
@@ -512,16 +496,8 @@ describe('WikiForm', () => {
});
});
- it('updates content from content editor on form submit', async () => {
- // old value
- expect(findContent().element.value).toBe(' My page content ');
-
- // wait for content editor to load
- await waitForPromises();
-
- await triggerFormSubmit();
-
- expect(findContent().element.value).toBe('hello **world**');
+ it('sets content field to the content editor updated markdown', async () => {
+ expect(findContent().element.value).toBe(updatedMarkdown);
});
});
});
diff --git a/spec/frontend/performance_bar/components/add_request_spec.js b/spec/frontend/performance_bar/components/add_request_spec.js
index 627e004ce3e..5460feb66fe 100644
--- a/spec/frontend/performance_bar/components/add_request_spec.js
+++ b/spec/frontend/performance_bar/components/add_request_spec.js
@@ -51,7 +51,7 @@ describe('add request form', () => {
});
it('emits an event to add the request', () => {
- expect(wrapper.emitted()['add-request']).toBeTruthy();
+ expect(wrapper.emitted()['add-request']).toHaveLength(1);
expect(wrapper.emitted()['add-request'][0]).toEqual([
'http://gitlab.example.com/users/root/calendar.json',
]);
diff --git a/spec/frontend/performance_bar/components/detailed_metric_spec.js b/spec/frontend/performance_bar/components/detailed_metric_spec.js
index 2ae36740dfb..437d51e02ba 100644
--- a/spec/frontend/performance_bar/components/detailed_metric_spec.js
+++ b/spec/frontend/performance_bar/components/detailed_metric_spec.js
@@ -257,7 +257,7 @@ describe('detailedMetric', () => {
});
it('displays request warnings', () => {
- expect(wrapper.find(RequestWarning).exists()).toBe(true);
+ expect(wrapper.findComponent(RequestWarning).exists()).toBe(true);
});
it('can open and close traces', async () => {
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index bff8fcda9b9..9cd5bb9e9a1 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -201,7 +201,7 @@ describe('PersistentUserCallout', () => {
await waitForPromises();
- expect(window.location.assign).toBeCalledWith(href);
+ expect(window.location.assign).toHaveBeenCalledWith(href);
expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
});
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
index bec6c2a8d0c..0ee6da9d329 100644
--- a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
+++ b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js
@@ -152,7 +152,7 @@ describe('Pipeline Editor | Commit Form', () => {
});
it('emits "scrolled-to-commit-form"', () => {
- expect(wrapper.emitted()['scrolled-to-commit-form']).toBeTruthy();
+ expect(wrapper.emitted()['scrolled-to-commit-form']).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
index 33c76309951..744b0378a75 100644
--- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
+++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
@@ -224,7 +224,7 @@ describe('Pipeline Editor | Commit section', () => {
});
it('emits a commit event with the right type, sourceBranch and targetBranch', () => {
- expect(wrapper.emitted('commit')).toBeTruthy();
+ expect(wrapper.emitted('commit')).toHaveLength(1);
expect(wrapper.emitted('commit')[0]).toMatchObject([
{
type: COMMIT_SUCCESS_WITH_REDIRECT,
diff --git a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
index 7dbacad34bf..8f6f4d8cff9 100644
--- a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
+++ b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
@@ -119,7 +119,7 @@ describe('Pipeline editor branch switcher', () => {
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findInfiniteScroll = () => wrapper.findComponent(GlInfiniteScroll);
diff --git a/spec/frontend/pipeline_editor/components/file-tree/container_spec.js b/spec/frontend/pipeline_editor/components/file-tree/container_spec.js
index 04a93e8db25..f79074f1e0f 100644
--- a/spec/frontend/pipeline_editor/components/file-tree/container_spec.js
+++ b/spec/frontend/pipeline_editor/components/file-tree/container_spec.js
@@ -31,7 +31,7 @@ describe('Pipeline editor file nav', () => {
const findTip = () => wrapper.findComponent(GlAlert);
const findCurrentConfigFilename = () => wrapper.findByTestId('current-config-filename');
- const fileTreeItems = () => wrapper.findAll(PipelineEditorFileTreeItem);
+ const fileTreeItems = () => wrapper.findAllComponents(PipelineEditorFileTreeItem);
afterEach(() => {
localStorage.clear();
diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
new file mode 100644
index 00000000000..d40a9cc8100
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
@@ -0,0 +1,109 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
+import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
+import { PIPELINE_FAILURE } from '~/pipeline_editor/constants';
+import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data';
+
+Vue.use(VueApollo);
+
+describe('Pipeline Status', () => {
+ let wrapper;
+ let mockApollo;
+ let mockLinkedPipelinesQuery;
+
+ const createComponent = ({ hasStages = true, options } = {}) => {
+ wrapper = shallowMount(PipelineEditorMiniGraph, {
+ provide: {
+ dataMethod: 'graphql',
+ projectFullPath: mockProjectFullPath,
+ },
+ propsData: {
+ pipeline: mockProjectPipeline({ hasStages }).pipeline,
+ },
+ ...options,
+ });
+ };
+
+ const createComponentWithApollo = (hasStages = true) => {
+ const handlers = [[getLinkedPipelinesQuery, mockLinkedPipelinesQuery]];
+ mockApollo = createMockApollo(handlers);
+
+ createComponent({
+ hasStages,
+ options: {
+ apolloProvider: mockApollo,
+ },
+ });
+ };
+
+ const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+
+ beforeEach(() => {
+ mockLinkedPipelinesQuery = jest.fn();
+ });
+
+ afterEach(() => {
+ mockLinkedPipelinesQuery.mockReset();
+ wrapper.destroy();
+ });
+
+ describe('when there are stages', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders pipeline mini graph', () => {
+ expect(findPipelineMiniGraph().exists()).toBe(true);
+ });
+ });
+
+ describe('when there are no stages', () => {
+ beforeEach(() => {
+ createComponent({ hasStages: false });
+ });
+
+ it('does not render pipeline mini graph', () => {
+ expect(findPipelineMiniGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('when querying upstream and downstream pipelines', () => {
+ describe('when query succeeds', () => {
+ beforeEach(() => {
+ mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines());
+ createComponentWithApollo();
+ });
+
+ it('should call the query with the correct variables', () => {
+ expect(mockLinkedPipelinesQuery).toHaveBeenCalledTimes(1);
+ expect(mockLinkedPipelinesQuery).toHaveBeenCalledWith({
+ fullPath: mockProjectFullPath,
+ iid: mockProjectPipeline().pipeline.iid,
+ });
+ });
+ });
+
+ describe('when query fails', () => {
+ beforeEach(async () => {
+ mockLinkedPipelinesQuery.mockRejectedValue(new Error());
+ createComponentWithApollo();
+ await waitForPromises();
+ });
+
+ it('should emit an error event when query fails', async () => {
+ expect(wrapper.emitted('showError')).toHaveLength(1);
+ expect(wrapper.emitted('showError')[0]).toEqual([
+ {
+ type: PIPELINE_FAILURE,
+ reasons: [wrapper.vm.$options.i18n.linkedPipelinesFetchError],
+ },
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js
index 93eb18c90cf..d40a9cc8100 100644
--- a/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
import { PIPELINE_FAILURE } from '~/pipeline_editor/constants';
import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data';
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 82ac390971d..7f89eda4dff 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
@@ -24,11 +24,11 @@ describe('CI Lint Results', () => {
});
};
- const findTable = () => wrapper.find(GlTableLite);
+ const findTable = () => wrapper.findComponent(GlTableLite);
const findByTestId = (selector) => () => wrapper.find(`[data-testid="ci-lint-${selector}"]`);
const findAllByTestId = (selector) => () =>
wrapper.findAll(`[data-testid="ci-lint-${selector}"]`);
- const findLinkToDoc = () => wrapper.find(GlLink);
+ const findLinkToDoc = () => wrapper.findComponent(GlLink);
const findErrors = findByTestId('errors');
const findWarnings = findByTestId('warnings');
const findStatus = findByTestId('status');
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 4b576508ee9..36052a2e16a 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
@@ -17,9 +17,9 @@ describe('CI lint warnings', () => {
});
};
- const findWarningAlert = () => wrapper.find(GlAlert);
+ const findWarningAlert = () => wrapper.findComponent(GlAlert);
const findWarnings = () => wrapper.findAll('[data-testid="ci-lint-warning"]');
- const findWarningMessage = () => trimText(wrapper.find(GlSprintf).text());
+ const findWarningMessage = () => trimText(wrapper.findComponent(GlSprintf).text());
afterEach(() => {
wrapper.destroy();
diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
index 2f3e1b49b37..3b79739630d 100644
--- a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -256,7 +256,7 @@ describe('Pipeline editor tabs component', () => {
${EDITOR_APP_STATUS_INVALID} | ${true} | ${false} | ${true} | ${false}
${EDITOR_APP_STATUS_VALID} | ${true} | ${true} | ${true} | ${true}
`(
- 'when status is $appStatus, we show - editor:$editor | viz:$viz | validate:$validate | merged:$merged ',
+ 'when status is $appStatus, we show - editor:$editor | viz:$viz | validate:$validate | merged:$merged',
({ appStatus, editor, viz, validate, merged }) => {
createComponent({ appStatus });
diff --git a/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js b/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js
index 8d172a8462a..b86c82850c5 100644
--- a/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js
+++ b/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js
@@ -23,7 +23,7 @@ describe('WalkthroughPopover component', () => {
});
it('emits "walkthrough-popover-cta-clicked" event', async () => {
- expect(wrapper.emitted()['walkthrough-popover-cta-clicked']).toBeTruthy();
+ expect(wrapper.emitted()['walkthrough-popover-cta-clicked']).toHaveLength(1);
});
});
});
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 3a40ce32a24..24f27e8c5fb 100644
--- a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
+++ b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
@@ -58,7 +58,7 @@ describe('~/pipeline_editor/components/ui/editor_tab.vue', () => {
const findSlotComponent = () => wrapper.findComponent(MockSourceEditor);
const findAlert = () => wrapper.findComponent(GlAlert);
- const findBadges = () => wrapper.findAll(GlBadge);
+ const findBadges = () => wrapper.findAllComponents(GlBadge);
beforeEach(() => {
mockChildMounted = jest.fn();
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index 0ce6cc3f2d4..1989f23a415 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -149,8 +149,7 @@ describe('Pipeline editor app component', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findEditorHome = () => wrapper.findComponent(PipelineEditorHome);
const findEmptyState = () => wrapper.findComponent(PipelineEditorEmptyState);
- const findEmptyStateButton = () =>
- wrapper.findComponent(PipelineEditorEmptyState).findComponent(GlButton);
+ const findEmptyStateButton = () => findEmptyState().findComponent(GlButton);
const findValidationSegment = () => wrapper.findComponent(ValidationSegment);
beforeEach(() => {
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
index 0cb7155c8c0..e317d1ddcc2 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
@@ -254,7 +254,7 @@ describe('Pipeline editor home wrapper', () => {
expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
- findPipelineEditorDrawer().find(GlDrawer).vm.$emit('close');
+ findPipelineEditorDrawer().findComponent(GlDrawer).vm.$emit('close');
await nextTick();
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
diff --git a/spec/frontend/pipeline_new/components/legacy_pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/legacy_pipeline_new_form_spec.js
new file mode 100644
index 00000000000..512b152f106
--- /dev/null
+++ b/spec/frontend/pipeline_new/components/legacy_pipeline_new_form_spec.js
@@ -0,0 +1,456 @@
+import { GlForm, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
+import CreditCardValidationRequiredAlert from 'ee_component/billings/components/cc_validation_required_alert.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+import { redirectTo } from '~/lib/utils/url_utility';
+import LegacyPipelineNewForm from '~/pipeline_new/components/legacy_pipeline_new_form.vue';
+import RefsDropdown from '~/pipeline_new/components/refs_dropdown.vue';
+import {
+ mockQueryParams,
+ mockPostParams,
+ mockProjectId,
+ mockError,
+ mockRefs,
+ mockCreditCardValidationRequiredError,
+} from '../mock_data';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+}));
+
+const projectRefsEndpoint = '/root/project/refs';
+const pipelinesPath = '/root/project/-/pipelines';
+const configVariablesPath = '/root/project/-/pipelines/config_variables';
+const newPipelinePostResponse = { id: 1 };
+const defaultBranch = 'main';
+
+describe('Pipeline New Form', () => {
+ let wrapper;
+ let mock;
+ let dummySubmitEvent;
+
+ const findForm = () => wrapper.findComponent(GlForm);
+ const findRefsDropdown = () => wrapper.findComponent(RefsDropdown);
+ 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 findDropdowns = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-type"]');
+ const findKeyInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-key"]');
+ const findValueInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-value"]');
+ const findErrorAlert = () => wrapper.find('[data-testid="run-pipeline-error-alert"]');
+ const findWarningAlert = () => wrapper.find('[data-testid="run-pipeline-warning-alert"]');
+ const findWarningAlertSummary = () => findWarningAlert().findComponent(GlSprintf);
+ const findWarnings = () => wrapper.findAll('[data-testid="run-pipeline-warning"]');
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findCCAlert = () => wrapper.findComponent(CreditCardValidationRequiredAlert);
+ const getFormPostParams = () => JSON.parse(mock.history.post[0].data);
+
+ const selectBranch = (branch) => {
+ // Select a branch in the dropdown
+ findRefsDropdown().vm.$emit('input', {
+ shortName: branch,
+ fullName: `refs/heads/${branch}`,
+ });
+ };
+
+ const createComponent = (props = {}, method = shallowMount) => {
+ wrapper = method(LegacyPipelineNewForm, {
+ provide: {
+ projectRefsEndpoint,
+ },
+ propsData: {
+ projectId: mockProjectId,
+ pipelinesPath,
+ configVariablesPath,
+ defaultBranch,
+ refParam: defaultBranch,
+ settingsLink: '',
+ maxWarnings: 25,
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {});
+ mock.onGet(projectRefsEndpoint).reply(httpStatusCodes.OK, mockRefs);
+
+ dummySubmitEvent = {
+ preventDefault: jest.fn(),
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+
+ mock.restore();
+ });
+
+ describe('Form', () => {
+ beforeEach(async () => {
+ createComponent(mockQueryParams, mount);
+
+ mock.onPost(pipelinesPath).reply(httpStatusCodes.OK, newPipelinePostResponse);
+
+ await waitForPromises();
+ });
+
+ it('displays the correct values for the provided query params', async () => {
+ expect(findDropdowns().at(0).props('text')).toBe('Variable');
+ expect(findDropdowns().at(1).props('text')).toBe('File');
+ expect(findRefsDropdown().props('value')).toEqual({ shortName: 'tag-1' });
+ expect(findVariableRows()).toHaveLength(3);
+ });
+
+ it('displays a variable from provided query params', () => {
+ expect(findKeyInputs().at(0).element.value).toBe('test_var');
+ expect(findValueInputs().at(0).element.value).toBe('test_var_val');
+ });
+
+ it('displays an empty variable for the user to fill out', async () => {
+ expect(findKeyInputs().at(2).element.value).toBe('');
+ expect(findValueInputs().at(2).element.value).toBe('');
+ expect(findDropdowns().at(2).props('text')).toBe('Variable');
+ });
+
+ it('does not display remove icon for last row', () => {
+ expect(findRemoveIcons()).toHaveLength(2);
+ });
+
+ it('removes ci variable row on remove icon button click', async () => {
+ findRemoveIcons().at(1).trigger('click');
+
+ await nextTick();
+
+ expect(findVariableRows()).toHaveLength(2);
+ });
+
+ it('creates blank variable on input change event', async () => {
+ const input = findKeyInputs().at(2);
+ input.element.value = 'test_var_2';
+ input.trigger('change');
+
+ await nextTick();
+
+ expect(findVariableRows()).toHaveLength(4);
+ expect(findKeyInputs().at(3).element.value).toBe('');
+ expect(findValueInputs().at(3).element.value).toBe('');
+ });
+ });
+
+ describe('Pipeline creation', () => {
+ beforeEach(async () => {
+ mock.onPost(pipelinesPath).reply(httpStatusCodes.OK, newPipelinePostResponse);
+
+ await waitForPromises();
+ });
+
+ it('does not submit the native HTML form', async () => {
+ createComponent();
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+
+ expect(dummySubmitEvent.preventDefault).toHaveBeenCalled();
+ });
+
+ 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();
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+ await waitForPromises();
+
+ expect(getFormPostParams().ref).toEqual(`refs/heads/${defaultBranch}`);
+ expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${newPipelinePostResponse.id}`);
+ });
+
+ it('creates a pipeline with short ref and variables from the query params', async () => {
+ createComponent(mockQueryParams);
+
+ await waitForPromises();
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+
+ await waitForPromises();
+
+ expect(getFormPostParams()).toEqual(mockPostParams);
+ expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${newPipelinePostResponse.id}`);
+ });
+ });
+
+ describe('When the ref has been changed', () => {
+ beforeEach(async () => {
+ createComponent({}, mount);
+
+ await waitForPromises();
+ });
+ it('variables persist between ref changes', async () => {
+ selectBranch('main');
+
+ await waitForPromises();
+
+ const mainInput = findKeyInputs().at(0);
+ mainInput.element.value = 'build_var';
+ mainInput.trigger('change');
+
+ await nextTick();
+
+ selectBranch('branch-1');
+
+ await waitForPromises();
+
+ const branchOneInput = findKeyInputs().at(0);
+ branchOneInput.element.value = 'deploy_var';
+ branchOneInput.trigger('change');
+
+ await nextTick();
+
+ selectBranch('main');
+
+ await waitForPromises();
+
+ expect(findKeyInputs().at(0).element.value).toBe('build_var');
+ expect(findVariableRows().length).toBe(2);
+
+ selectBranch('branch-1');
+
+ await waitForPromises();
+
+ expect(findKeyInputs().at(0).element.value).toBe('deploy_var');
+ expect(findVariableRows().length).toBe(2);
+ });
+ });
+
+ 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.';
+
+ it('loading icon is shown when content is requested and hidden when received', async () => {
+ createComponent(mockQueryParams, mount);
+
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
+ [mockYmlKey]: {
+ value: mockYmlValue,
+ description: mockYmlDesc,
+ },
+ });
+
+ expect(findLoadingIcon().exists()).toBe(true);
+
+ await waitForPromises();
+
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
+ it('multi-line strings are added to the value field without removing line breaks', async () => {
+ createComponent(mockQueryParams, mount);
+
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
+ [mockYmlKey]: {
+ value: mockYmlMultiLineValue,
+ description: mockYmlDesc,
+ },
+ });
+
+ await waitForPromises();
+
+ expect(findValueInputs().at(0).element.value).toBe(mockYmlMultiLineValue);
+ });
+
+ describe('with description', () => {
+ beforeEach(async () => {
+ createComponent(mockQueryParams, mount);
+
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
+ [mockYmlKey]: {
+ value: mockYmlValue,
+ description: mockYmlDesc,
+ },
+ });
+
+ await waitForPromises();
+ });
+
+ it('displays all the variables', async () => {
+ expect(findVariableRows()).toHaveLength(4);
+ });
+
+ it('displays a variable from yml', () => {
+ expect(findKeyInputs().at(0).element.value).toBe(mockYmlKey);
+ expect(findValueInputs().at(0).element.value).toBe(mockYmlValue);
+ });
+
+ it('displays a variable from provided query params', () => {
+ expect(findKeyInputs().at(1).element.value).toBe('test_var');
+ expect(findValueInputs().at(1).element.value).toBe('test_var_val');
+ });
+
+ it('adds a description to the first variable from yml', () => {
+ expect(findVariableRows().at(0).text()).toContain(mockYmlDesc);
+ });
+
+ it('removes the description when a variable key changes', async () => {
+ findKeyInputs().at(0).element.value = 'yml_var_modified';
+ findKeyInputs().at(0).trigger('change');
+
+ await nextTick();
+
+ expect(findVariableRows().at(0).text()).not.toContain(mockYmlDesc);
+ });
+ });
+
+ describe('without description', () => {
+ beforeEach(async () => {
+ createComponent(mockQueryParams, mount);
+
+ mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, {
+ [mockYmlKey]: {
+ value: mockYmlValue,
+ description: null,
+ },
+ yml_var2: {
+ value: 'yml_var2_val',
+ },
+ yml_var3: {
+ description: '',
+ },
+ });
+
+ await waitForPromises();
+ });
+
+ it('displays all the variables', async () => {
+ expect(findVariableRows()).toHaveLength(3);
+ });
+ });
+ });
+
+ describe('Form errors and warnings', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('when the refs cannot be loaded', () => {
+ beforeEach(() => {
+ mock
+ .onGet(projectRefsEndpoint, { params: { search: '' } })
+ .reply(httpStatusCodes.INTERNAL_SERVER_ERROR);
+
+ findRefsDropdown().vm.$emit('loadingError');
+ });
+
+ it('shows both an error alert', () => {
+ expect(findErrorAlert().exists()).toBe(true);
+ expect(findWarningAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('when the error response can be handled', () => {
+ beforeEach(async () => {
+ mock.onPost(pipelinesPath).reply(httpStatusCodes.BAD_REQUEST, mockError);
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+
+ await waitForPromises();
+ });
+
+ 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 warning title', () => {
+ const { length } = mockError.warnings;
+
+ 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('does not show the credit card validation required alert', () => {
+ expect(findCCAlert().exists()).toBe(false);
+ });
+
+ describe('when the error response is credit card validation required', () => {
+ beforeEach(async () => {
+ mock
+ .onPost(pipelinesPath)
+ .reply(httpStatusCodes.BAD_REQUEST, mockCreditCardValidationRequiredError);
+
+ window.gon = {
+ subscriptions_url: TEST_HOST,
+ payment_form_url: TEST_HOST,
+ };
+
+ findForm().vm.$emit('submit', dummySubmitEvent);
+
+ await waitForPromises();
+ });
+
+ it('shows credit card validation required alert', () => {
+ expect(findErrorAlert().exists()).toBe(false);
+ expect(findCCAlert().exists()).toBe(true);
+ });
+
+ it('clears error and hides the alert on dismiss', async () => {
+ expect(findCCAlert().exists()).toBe(true);
+ expect(wrapper.vm.$data.error).toBe(mockCreditCardValidationRequiredError.errors[0]);
+
+ findCCAlert().vm.$emit('dismiss');
+
+ await nextTick();
+
+ expect(findCCAlert().exists()).toBe(false);
+ expect(wrapper.vm.$data.error).toBe(null);
+ });
+ });
+ });
+
+ 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/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
index 18dbd1ce9d6..5ce29bd6c5d 100644
--- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
@@ -34,7 +34,7 @@ describe('Pipeline New Form', () => {
let mock;
let dummySubmitEvent;
- const findForm = () => wrapper.find(GlForm);
+ const findForm = () => wrapper.findComponent(GlForm);
const findRefsDropdown = () => wrapper.findComponent(RefsDropdown);
const findSubmitButton = () => wrapper.find('[data-testid="run_pipeline_button"]');
const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]');
@@ -44,9 +44,9 @@ describe('Pipeline New Form', () => {
const findValueInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-value"]');
const findErrorAlert = () => wrapper.find('[data-testid="run-pipeline-error-alert"]');
const findWarningAlert = () => wrapper.find('[data-testid="run-pipeline-warning-alert"]');
- const findWarningAlertSummary = () => findWarningAlert().find(GlSprintf);
+ const findWarningAlertSummary = () => findWarningAlert().findComponent(GlSprintf);
const findWarnings = () => wrapper.findAll('[data-testid="run-pipeline-warning"]');
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findCCAlert = () => wrapper.findComponent(CreditCardValidationRequiredAlert);
const getFormPostParams = () => JSON.parse(mock.history.post[0].data);
@@ -329,6 +329,12 @@ describe('Pipeline New Form', () => {
value: mockYmlValue,
description: null,
},
+ yml_var2: {
+ value: 'yml_var2_val',
+ },
+ yml_var3: {
+ description: '',
+ },
});
await waitForPromises();
diff --git a/spec/frontend/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/pipeline_new/components/refs_dropdown_spec.js
index 826f2826d3c..8cba876c688 100644
--- a/spec/frontend/pipeline_new/components/refs_dropdown_spec.js
+++ b/spec/frontend/pipeline_new/components/refs_dropdown_spec.js
@@ -19,8 +19,8 @@ describe('Pipeline New Form', () => {
let wrapper;
let mock;
- const findDropdown = () => wrapper.find(GlDropdown);
- const findRefsDropdownItems = () => wrapper.findAll(GlDropdownItem);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findRefsDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const createComponent = (props = {}, mountFn = shallowMount) => {
diff --git a/spec/frontend/pipeline_wizard/components/commit_spec.js b/spec/frontend/pipeline_wizard/components/commit_spec.js
index c987accbb0d..d7e019c642e 100644
--- a/spec/frontend/pipeline_wizard/components/commit_spec.js
+++ b/spec/frontend/pipeline_wizard/components/commit_spec.js
@@ -174,7 +174,7 @@ describe('Pipeline Wizard - Commit Page', () => {
});
it('will not emit a done event', () => {
- expect(wrapper.emitted().done?.length).toBeFalsy();
+ expect(wrapper.emitted().done?.length).toBeUndefined();
});
afterEach(() => {
diff --git a/spec/frontend/pipeline_wizard/components/editor_spec.js b/spec/frontend/pipeline_wizard/components/editor_spec.js
index 540a08d2c7f..26e4b8eb0ea 100644
--- a/spec/frontend/pipeline_wizard/components/editor_spec.js
+++ b/spec/frontend/pipeline_wizard/components/editor_spec.js
@@ -11,7 +11,7 @@ describe('Pages Yaml Editor wrapper', () => {
const wrapper = mount(YamlEditor, defaultOptions);
it('editor is mounted', () => {
- expect(wrapper.vm.editor).not.toBeFalsy();
+ expect(wrapper.vm.editor).not.toBeUndefined();
expect(wrapper.find('.gl-source-editor').exists()).toBe(true);
});
});
@@ -57,13 +57,4 @@ describe('Pages Yaml Editor wrapper', () => {
});
});
});
-
- describe('events', () => {
- const wrapper = mount(YamlEditor, defaultOptions);
-
- it('emits touch if content is changed in editor', async () => {
- await wrapper.vm.editor.setValue('foo: boo');
- expect(wrapper.emitted('touch')).toEqual([expect.any(Array)]);
- });
- });
});
diff --git a/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js b/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js
index ea2448b1362..f288264a11e 100644
--- a/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js
+++ b/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js
@@ -30,7 +30,7 @@ describe('Pipeline Wizard -- Input Wrapper', () => {
beforeEach(() => {
createComponent({});
- inputChild = wrapper.find(TextWidget);
+ inputChild = wrapper.findComponent(TextWidget);
});
afterEach(() => {
diff --git a/spec/frontend/pipeline_wizard/components/wrapper_spec.js b/spec/frontend/pipeline_wizard/components/wrapper_spec.js
index 357a9d21723..f064bf01c86 100644
--- a/spec/frontend/pipeline_wizard/components/wrapper_spec.js
+++ b/spec/frontend/pipeline_wizard/components/wrapper_spec.js
@@ -2,6 +2,7 @@ import { Document, parseDocument } from 'yaml';
import { GlProgressBar } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking } from 'helpers/tracking_helper';
import PipelineWizardWrapper, { i18n } from '~/pipeline_wizard/components/wrapper.vue';
import WizardStep from '~/pipeline_wizard/components/step.vue';
import CommitStep from '~/pipeline_wizard/components/commit.vue';
@@ -19,9 +20,11 @@ describe('Pipeline Wizard - wrapper.vue', () => {
const steps = parseDocument(stepsYaml).toJS();
const getAsYamlNode = (value) => new Document(value).contents;
+ const templateId = 'my-namespace/my-template';
const createComponent = (props = {}, mountFn = shallowMountExtended) => {
wrapper = mountFn(PipelineWizardWrapper, {
propsData: {
+ templateId,
projectPath: '/user/repo',
defaultBranch: 'main',
filename: '.gitlab-ci.yml',
@@ -311,4 +314,126 @@ describe('Pipeline Wizard - wrapper.vue', () => {
});
});
});
+
+ describe('when commit step done', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('emits done', () => {
+ expect(wrapper.emitted('done')).toBeUndefined();
+
+ wrapper.findComponent(CommitStep).vm.$emit('done');
+
+ expect(wrapper.emitted('done')).toHaveLength(1);
+ });
+ });
+
+ describe('tracking', () => {
+ let trackingSpy;
+ const trackingCategory = `pipeline_wizard:${templateId}`;
+
+ const setUpTrackingSpy = () => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ };
+
+ it('tracks next button click event', () => {
+ createComponent();
+ setUpTrackingSpy();
+ findFirstVisibleStep().vm.$emit('next');
+
+ expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
+ category: trackingCategory,
+ property: 'next',
+ label: 'pipeline_wizard_navigation',
+ extra: {
+ fromStep: 0,
+ toStep: 1,
+ },
+ });
+ });
+
+ it('tracks back button click event', () => {
+ createComponent();
+
+ // Navigate to step 1 without the spy set up
+ findFirstVisibleStep().vm.$emit('next');
+
+ // Now enable the tracking spy
+ setUpTrackingSpy();
+
+ findFirstVisibleStep().vm.$emit('back');
+
+ expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
+ category: trackingCategory,
+ property: 'back',
+ label: 'pipeline_wizard_navigation',
+ extra: {
+ fromStep: 1,
+ toStep: 0,
+ },
+ });
+ });
+
+ it('tracks back button click event on the commit step', () => {
+ createComponent();
+
+ // Navigate to step 2 without the spy set up
+ findFirstVisibleStep().vm.$emit('next');
+ findFirstVisibleStep().vm.$emit('next');
+
+ // Now enable the tracking spy
+ setUpTrackingSpy();
+
+ wrapper.findComponent(CommitStep).vm.$emit('back');
+
+ expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
+ category: trackingCategory,
+ property: 'back',
+ label: 'pipeline_wizard_navigation',
+ extra: {
+ fromStep: 2,
+ toStep: 1,
+ },
+ });
+ });
+
+ it('tracks done event on the commit step', () => {
+ createComponent();
+
+ // Navigate to step 2 without the spy set up
+ findFirstVisibleStep().vm.$emit('next');
+ findFirstVisibleStep().vm.$emit('next');
+
+ // Now enable the tracking spy
+ setUpTrackingSpy();
+
+ wrapper.findComponent(CommitStep).vm.$emit('done');
+
+ expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
+ category: trackingCategory,
+ label: 'pipeline_wizard_commit',
+ property: 'commit',
+ });
+ });
+
+ it('tracks when editor emits touch events', () => {
+ createComponent();
+ setUpTrackingSpy();
+
+ wrapper.findComponent(YamlEditor).vm.$emit('touch');
+
+ expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'edit', {
+ category: trackingCategory,
+ label: 'pipeline_wizard_editor_interaction',
+ extra: {
+ currentStep: 0,
+ },
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipeline_wizard/mock/yaml.js b/spec/frontend/pipeline_wizard/mock/yaml.js
index e7087b59ce7..12b6f1052b2 100644
--- a/spec/frontend/pipeline_wizard/mock/yaml.js
+++ b/spec/frontend/pipeline_wizard/mock/yaml.js
@@ -71,6 +71,7 @@ bar: barVal
`;
export const fullTemplate = `
+id: test/full-template
title: some title
description: some description
filename: foo.yml
@@ -84,6 +85,7 @@ steps:
`;
export const fullTemplateWithoutFilename = `
+id: test/full-template-no-filename
title: some title
description: some description
steps:
diff --git a/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js b/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js
index 3f689ffdbc8..13234525159 100644
--- a/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js
+++ b/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js
@@ -59,6 +59,7 @@ describe('PipelineWizard', () => {
defaultBranch,
projectPath,
filename: parseDocument(template).get('filename'),
+ templateId: parseDocument(template).get('id'),
}),
);
});
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
index 212f8e19a6d..28a08b6da0f 100644
--- a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -11,7 +11,7 @@ describe('The DAG annotations', () => {
const getAllColorBlocks = () => wrapper.findAll('[data-testid="dag-color-block"]');
const getTextBlock = () => wrapper.find('[data-testid="dag-note-text"]');
const getAllTextBlocks = () => wrapper.findAll('[data-testid="dag-note-text"]');
- const getToggleButton = () => wrapper.find(GlButton);
+ const getToggleButton = () => wrapper.findComponent(GlButton);
const createComponent = (propsData = {}, method = shallowMount) => {
if (wrapper?.destroy) {
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index d78df3eb35e..b0c26976c85 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -18,12 +18,12 @@ import {
describe('Pipeline DAG graph wrapper', () => {
let wrapper;
- const getAlert = () => wrapper.find(GlAlert);
- const getAllAlerts = () => wrapper.findAll(GlAlert);
- const getGraph = () => wrapper.find(DagGraph);
- const getNotes = () => wrapper.find(DagAnnotations);
+ const getAlert = () => wrapper.findComponent(GlAlert);
+ const getAllAlerts = () => wrapper.findAllComponents(GlAlert);
+ const getGraph = () => wrapper.findComponent(DagGraph);
+ const getNotes = () => wrapper.findComponent(DagAnnotations);
const getErrorText = (type) => wrapper.vm.$options.errorTexts[type];
- const getEmptyState = () => wrapper.find(GlEmptyState);
+ const getEmptyState = () => wrapper.findComponent(GlEmptyState);
const createComponent = ({
graphData = mockParsedGraphQLNodes,
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
new file mode 100644
index 00000000000..5ea57c51e70
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
@@ -0,0 +1,176 @@
+import { mount } from '@vue/test-utils';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import LinkedPipelinesMiniList from '~/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue';
+import mockData from './linked_pipelines_mock_data';
+
+describe('Linked pipeline mini list', () => {
+ let wrapper;
+
+ const findCiIcon = () => wrapper.findComponent(CiIcon);
+ const findCiIcons = () => wrapper.findAllComponents(CiIcon);
+ const findLinkedPipelineCounter = () => wrapper.find('[data-testid="linked-pipeline-counter"]');
+ const findLinkedPipelineMiniItem = () =>
+ wrapper.find('[data-testid="linked-pipeline-mini-item"]');
+ const findLinkedPipelineMiniItems = () =>
+ wrapper.findAll('[data-testid="linked-pipeline-mini-item"]');
+ const findLinkedPipelineMiniList = () => wrapper.findComponent(LinkedPipelinesMiniList);
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(LinkedPipelinesMiniList, {
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ describe('when passed an upstream pipeline as prop', () => {
+ beforeEach(() => {
+ createComponent({
+ triggeredBy: [mockData.triggered_by],
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render one linked pipeline item', () => {
+ expect(findLinkedPipelineMiniItem().exists()).toBe(true);
+ });
+
+ it('should render a linked pipeline with the correct href', () => {
+ expect(findLinkedPipelineMiniItem().exists()).toBe(true);
+
+ expect(findLinkedPipelineMiniItem().attributes('href')).toBe(
+ '/gitlab-org/gitlab-foss/-/pipelines/129',
+ );
+ });
+
+ it('should render one ci status icon', () => {
+ expect(findCiIcon().exists()).toBe(true);
+ });
+
+ it('should render a borderless ci-icon', () => {
+ expect(findCiIcon().exists()).toBe(true);
+
+ expect(findCiIcon().props('isBorderless')).toBe(true);
+ expect(findCiIcon().classes('borderless')).toBe(true);
+ });
+
+ it('should render a ci-icon with a custom border class', () => {
+ expect(findCiIcon().exists()).toBe(true);
+
+ expect(findCiIcon().classes('gl-border')).toBe(true);
+ });
+
+ it('should render the correct ci status icon', () => {
+ expect(findCiIcon().classes('ci-status-icon-running')).toBe(true);
+ });
+
+ it('should have an activated tooltip', () => {
+ expect(findLinkedPipelineMiniItem().exists()).toBe(true);
+ const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip');
+
+ expect(tooltip.value.title).toBe('GitLabCE - running');
+ });
+
+ it('should correctly set is-upstream', () => {
+ expect(findLinkedPipelineMiniList().exists()).toBe(true);
+
+ expect(findLinkedPipelineMiniList().classes('is-upstream')).toBe(true);
+ });
+
+ it('should correctly compute shouldRenderCounter', () => {
+ expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(false);
+ });
+
+ it('should not render the pipeline counter', () => {
+ expect(findLinkedPipelineCounter().exists()).toBe(false);
+ });
+ });
+
+ describe('when passed downstream pipelines as props', () => {
+ beforeEach(() => {
+ createComponent({
+ triggered: mockData.triggered,
+ pipelinePath: 'my/pipeline/path',
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render three linked pipeline items', () => {
+ expect(findLinkedPipelineMiniItems().exists()).toBe(true);
+ expect(findLinkedPipelineMiniItems().length).toBe(3);
+ });
+
+ it('should render three ci status icons', () => {
+ expect(findCiIcons().exists()).toBe(true);
+ expect(findCiIcons().length).toBe(3);
+ });
+
+ it('should render the correct ci status icon', () => {
+ expect(findCiIcon().classes('ci-status-icon-running')).toBe(true);
+ });
+
+ it('should have an activated tooltip', () => {
+ expect(findLinkedPipelineMiniItem().exists()).toBe(true);
+ const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip');
+
+ expect(tooltip.value.title).toBe('GitLabCE - running');
+ });
+
+ it('should correctly set is-downstream', () => {
+ expect(findLinkedPipelineMiniList().exists()).toBe(true);
+
+ expect(findLinkedPipelineMiniList().classes('is-downstream')).toBe(true);
+ });
+
+ it('should render a borderless ci-icon', () => {
+ expect(findCiIcon().exists()).toBe(true);
+
+ expect(findCiIcon().props('isBorderless')).toBe(true);
+ expect(findCiIcon().classes('borderless')).toBe(true);
+ });
+
+ it('should render a ci-icon with a custom border class', () => {
+ expect(findCiIcon().exists()).toBe(true);
+
+ expect(findCiIcon().classes('gl-border')).toBe(true);
+ });
+
+ it('should render the pipeline counter', () => {
+ expect(findLinkedPipelineCounter().exists()).toBe(true);
+ });
+
+ it('should correctly compute shouldRenderCounter', () => {
+ expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(true);
+ });
+
+ it('should correctly trim linkedPipelines', () => {
+ expect(findLinkedPipelineMiniList().props('triggered').length).toBe(6);
+ expect(findLinkedPipelineMiniList().vm.linkedPipelinesTrimmed.length).toBe(3);
+ });
+
+ it('should set the correct pipeline path', () => {
+ expect(findLinkedPipelineCounter().exists()).toBe(true);
+
+ expect(findLinkedPipelineCounter().attributes('href')).toBe('my/pipeline/path');
+ });
+
+ it('should render the correct counterTooltipText', () => {
+ expect(findLinkedPipelineCounter().exists()).toBe(true);
+ const tooltip = getBinding(findLinkedPipelineCounter().element, 'gl-tooltip');
+
+ expect(tooltip.value.title).toBe(findLinkedPipelineMiniList().vm.counterTooltipText);
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mock_data.js b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mock_data.js
new file mode 100644
index 00000000000..117c7f2ae52
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mock_data.js
@@ -0,0 +1,407 @@
+export default {
+ triggered_by: {
+ id: 129,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/129',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/129',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: '7-5-stable',
+ path: '/gitlab-org/gitlab-foss/commits/7-5-stable',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '23433d4d8b20d7e45c103d0b6048faad38a130ab',
+ short_id: '23433d4d',
+ title: 'Version 7.5.0.rc1',
+ created_at: '2014-11-17T15:44:14.000+01:00',
+ parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'],
+ message: 'Version 7.5.0.rc1\n',
+ author_name: 'Jacob Vosmaer',
+ author_email: 'contact@jacobvosmaer.nl',
+ authored_date: '2014-11-17T15:44:14.000+01:00',
+ committer_name: 'Jacob Vosmaer',
+ committer_email: 'contact@jacobvosmaer.nl',
+ committed_date: '2014-11-17T15:44:14.000+01:00',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
+ commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/129/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/129/cancel',
+ created_at: '2017-05-24T14:46:20.090Z',
+ updated_at: '2017-05-24T14:46:29.906Z',
+ },
+ triggered: [
+ {
+ id: 132,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ short_id: 'b9d58c4c',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-03T12:50:33.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-03T12:50:33.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/132/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/132/cancel',
+ created_at: '2017-05-24T14:46:24.644Z',
+ updated_at: '2017-05-24T14:48:55.226Z',
+ },
+ {
+ id: 133,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ short_id: 'b6bd4856',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-02T20:39:29.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-02T20:39:29.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/133/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/133/cancel',
+ created_at: '2017-05-24T14:46:24.648Z',
+ updated_at: '2017-05-24T14:48:59.673Z',
+ },
+ {
+ id: 130,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ short_id: '6d7ced4a',
+ title: 'Whitespace fixes to patch',
+ created_at: '2013-10-08T13:53:22.000-05:00',
+ parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
+ message: 'Whitespace fixes to patch\n',
+ author_name: 'Dale Hamel',
+ author_email: 'dale.hamel@srvthe.net',
+ authored_date: '2013-10-08T13:53:22.000-05:00',
+ committer_name: 'Dale Hamel',
+ committer_email: 'dale.hamel@invenia.ca',
+ committed_date: '2013-10-08T13:53:22.000-05:00',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/130/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/130/cancel',
+ created_at: '2017-05-24T14:46:24.630Z',
+ updated_at: '2017-05-24T14:49:45.091Z',
+ },
+ {
+ id: 131,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ short_id: 'b9d58c4c',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-03T12:50:33.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-03T12:50:33.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/132/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/132/cancel',
+ created_at: '2017-05-24T14:46:24.644Z',
+ updated_at: '2017-05-24T14:48:55.226Z',
+ },
+ {
+ id: 134,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ short_id: 'b6bd4856',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-02T20:39:29.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-02T20:39:29.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/133/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/133/cancel',
+ created_at: '2017-05-24T14:46:24.648Z',
+ updated_at: '2017-05-24T14:48:59.673Z',
+ },
+ {
+ id: 135,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ short_id: '6d7ced4a',
+ title: 'Whitespace fixes to patch',
+ created_at: '2013-10-08T13:53:22.000-05:00',
+ parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
+ message: 'Whitespace fixes to patch\n',
+ author_name: 'Dale Hamel',
+ author_email: 'dale.hamel@srvthe.net',
+ authored_date: '2013-10-08T13:53:22.000-05:00',
+ committer_name: 'Dale Hamel',
+ committer_email: 'dale.hamel@invenia.ca',
+ committed_date: '2013-10-08T13:53:22.000-05:00',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/-/pipelines/130/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/130/cancel',
+ created_at: '2017-05-24T14:46:24.630Z',
+ updated_at: '2017-05-24T14:49:45.091Z',
+ },
+ ],
+};
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
new file mode 100644
index 00000000000..7fa8a18ea1f
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
@@ -0,0 +1,149 @@
+import { mount } from '@vue/test-utils';
+import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
+import PipelineStages from '~/pipelines/components/pipeline_mini_graph/pipeline_stages.vue';
+import mockLinkedPipelines from './linked_pipelines_mock_data';
+
+const mockStages = pipelines[0].details.stages;
+
+describe('Pipeline Mini Graph', () => {
+ let wrapper;
+
+ const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+ const findPipelineStages = () => wrapper.findComponent(PipelineStages);
+
+ const findLinkedPipelineUpstream = () =>
+ wrapper.findComponent('[data-testid="pipeline-mini-graph-upstream"]');
+ const findLinkedPipelineDownstream = () =>
+ wrapper.findComponent('[data-testid="pipeline-mini-graph-downstream"]');
+ const findDownstreamArrowIcon = () => wrapper.find('[data-testid="downstream-arrow-icon"]');
+ const findUpstreamArrowIcon = () => wrapper.find('[data-testid="upstream-arrow-icon"]');
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(PipelineMiniGraph, {
+ propsData: {
+ stages: mockStages,
+ ...props,
+ },
+ });
+ };
+
+ describe('rendered state without upstream or downstream pipelines', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render the pipeline stages', () => {
+ expect(findPipelineStages().exists()).toBe(true);
+ });
+
+ it('should have the correct props', () => {
+ expect(findPipelineMiniGraph().props()).toMatchObject({
+ downstreamPipelines: [],
+ isMergeTrain: false,
+ pipelinePath: '',
+ stages: expect.any(Array),
+ stagesClass: '',
+ updateDropdown: false,
+ upstreamPipeline: undefined,
+ });
+ });
+
+ it('should have no linked pipelines', () => {
+ expect(findLinkedPipelineDownstream().exists()).toBe(false);
+ expect(findLinkedPipelineUpstream().exists()).toBe(false);
+ });
+
+ it('should not render arrow icons', () => {
+ expect(findUpstreamArrowIcon().exists()).toBe(false);
+ expect(findDownstreamArrowIcon().exists()).toBe(false);
+ });
+
+ it('triggers events in "action request complete"', () => {
+ createComponent();
+
+ findPipelineMiniGraph(0).vm.$emit('pipelineActionRequestComplete');
+ findPipelineMiniGraph(1).vm.$emit('pipelineActionRequestComplete');
+
+ expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(2);
+ });
+ });
+
+ describe('rendered state with upstream pipeline', () => {
+ beforeEach(() => {
+ createComponent({
+ upstreamPipeline: mockLinkedPipelines.triggered_by,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should have the correct props', () => {
+ expect(findPipelineMiniGraph().props()).toMatchObject({
+ downstreamPipelines: [],
+ isMergeTrain: false,
+ pipelinePath: '',
+ stages: expect.any(Array),
+ stagesClass: '',
+ updateDropdown: false,
+ upstreamPipeline: expect.any(Object),
+ });
+ });
+
+ it('should render the upstream linked pipelines mini list only', () => {
+ expect(findLinkedPipelineUpstream().exists()).toBe(true);
+ expect(findLinkedPipelineDownstream().exists()).toBe(false);
+ });
+
+ it('should render an upstream arrow icon only', () => {
+ expect(findDownstreamArrowIcon().exists()).toBe(false);
+ expect(findUpstreamArrowIcon().exists()).toBe(true);
+ expect(findUpstreamArrowIcon().props('name')).toBe('long-arrow');
+ });
+ });
+
+ describe('rendered state with downstream pipelines', () => {
+ beforeEach(() => {
+ createComponent({
+ downstreamPipelines: mockLinkedPipelines.triggered,
+ pipelinePath: 'my/pipeline/path',
+ });
+ });
+
+ it('should have the correct props', () => {
+ expect(findPipelineMiniGraph().props()).toMatchObject({
+ downstreamPipelines: expect.any(Array),
+ isMergeTrain: false,
+ pipelinePath: 'my/pipeline/path',
+ stages: expect.any(Array),
+ stagesClass: '',
+ updateDropdown: false,
+ upstreamPipeline: undefined,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render the downstream linked pipelines mini list only', () => {
+ expect(findLinkedPipelineDownstream().exists()).toBe(true);
+ expect(findLinkedPipelineUpstream().exists()).toBe(false);
+ });
+
+ it('should render a downstream arrow icon only', () => {
+ expect(findUpstreamArrowIcon().exists()).toBe(false);
+ expect(findDownstreamArrowIcon().exists()).toBe(true);
+ expect(findDownstreamArrowIcon().props('name')).toBe('long-arrow');
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
index e712cdeaea2..52b440f18bb 100644
--- a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
@@ -4,7 +4,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import axios from '~/lib/utils/axios_utils';
-import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue';
import eventHub from '~/pipelines/event_hub';
import waitForPromises from 'helpers/wait_for_promises';
import { stageReply } from '../../mock_data';
@@ -129,10 +129,11 @@ describe('Pipelines stage component', () => {
await axios.waitForAll();
});
- it('renders the received data and emit `clickedDropdown` event', async () => {
+ it('renders the received data and emits the correct events', async () => {
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
expect(findDropdownMenuTitle().text()).toContain(stageReply.name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
+ expect(wrapper.emitted('miniGraphStageClick')).toEqual([[]]);
});
it('refreshes when updateDropdown is set to true', async () => {
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
index 1cb43c199aa..bfb780d5d39 100644
--- a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
@@ -1,18 +1,18 @@
import { shallowMount } from '@vue/test-utils';
import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
-import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue';
+import PipelineStages from '~/pipelines/components/pipeline_mini_graph/pipeline_stages.vue';
const mockStages = pipelines[0].details.stages;
-describe('Pipeline Mini Graph', () => {
+describe('Pipeline Stages', () => {
let wrapper;
- const findPipelineStages = () => wrapper.findAll(PipelineStage);
+ const findPipelineStages = () => wrapper.findAllComponents(PipelineStage);
const findPipelineStagesAt = (i) => findPipelineStages().at(i);
const createComponent = (props = {}) => {
- wrapper = shallowMount(PipelineMiniGraph, {
+ wrapper = shallowMount(PipelineStages, {
propsData: {
stages: mockStages,
...props,
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index f958f12acd4..ee3eaaf5ef3 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -2,17 +2,19 @@ import { GlFilteredSearch } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
+import { TRACKING_CATEGORIES } from '~/pipelines/constants';
import { users, mockSearch, branches, tags } from '../mock_data';
describe('Pipelines filtered search', () => {
let wrapper;
let mock;
- const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
+ const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
const getSearchToken = (type) =>
findFilteredSearch()
.props('availableTokens')
@@ -177,4 +179,20 @@ describe('Pipelines filtered search', () => {
expect(findFilteredSearch().props('value')).toHaveLength(expectedValueProp.length);
});
});
+
+ describe('tracking', () => {
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks filtered search click', () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ findFilteredSearch().vm.$emit('submit', mockSearch);
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_filtered_search', {
+ label: TRACKING_CATEGORIES.search,
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js
index 6e5aa572ec0..a823e029281 100644
--- a/spec/frontend/pipelines/graph/action_component_spec.js
+++ b/spec/frontend/pipelines/graph/action_component_spec.js
@@ -9,7 +9,7 @@ import ActionComponent from '~/pipelines/components/jobs_shared/action_component
describe('pipeline graph action component', () => {
let wrapper;
let mock;
- const findButton = () => wrapper.find(GlButton);
+ const findButton = () => wrapper.findComponent(GlButton);
const findTooltipWrapper = () => wrapper.find('[data-testid="ci-action-icon-tooltip-wrapper"]');
beforeEach(() => {
diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js
index 4b2b61c8edd..2abb5f7dc58 100644
--- a/spec/frontend/pipelines/graph/graph_component_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_spec.js
@@ -15,9 +15,9 @@ import {
describe('graph component', () => {
let wrapper;
- const findLinkedColumns = () => wrapper.findAll(LinkedPipelinesColumn);
- const findLinksLayer = () => wrapper.find(LinksLayer);
- const findStageColumns = () => wrapper.findAll(StageColumnComponent);
+ const findLinkedColumns = () => wrapper.findAllComponents(LinkedPipelinesColumn);
+ const findLinksLayer = () => wrapper.findComponent(LinksLayer);
+ const findStageColumns = () => wrapper.findAllComponents(StageColumnComponent);
const findStageNameInJob = () => wrapper.find('[data-testid="stage-name-in-job"]');
const defaultProps = {
@@ -107,7 +107,7 @@ describe('graph component', () => {
});
it('dims unrelated jobs', () => {
- const unrelatedJob = wrapper.find(JobItem);
+ const unrelatedJob = wrapper.findComponent(JobItem);
expect(findLinksLayer().emitted().highlightedJobsChange).toHaveLength(1);
expect(unrelatedJob.classes('gl-opacity-3')).toBe(true);
});
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 3eaf06e0656..587a3c67168 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -30,16 +30,10 @@ import * as Api from '~/pipelines/components/graph_shared/api';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
import * as parsingUtils from '~/pipelines/components/parsing_utils';
import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql';
-import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql';
import * as sentryUtils from '~/pipelines/utils';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { mockRunningPipelineHeaderData } from '../mock_data';
-import {
- mapCallouts,
- mockCalloutsResponse,
- mockPipelineResponse,
- mockPerformanceInsightsResponse,
-} from './mock_data';
+import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data';
const defaultProvide = {
graphqlResourceEtag: 'frog/amphibirama/etag/',
@@ -57,11 +51,11 @@ describe('Pipeline graph wrapper', () => {
const getDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]');
const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const getLinksLayer = () => wrapper.findComponent(LinksLayer);
- const getGraph = () => wrapper.find(PipelineGraph);
+ const getGraph = () => wrapper.findComponent(PipelineGraph);
const getStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]');
const getAllStageColumnGroupsInColumn = () =>
- wrapper.find(StageColumnComponent).findAll('[data-testid="stage-column-group"]');
- const getViewSelector = () => wrapper.find(GraphViewSelector);
+ wrapper.findComponent(StageColumnComponent).findAll('[data-testid="stage-column-group"]');
+ const getViewSelector = () => wrapper.findComponent(GraphViewSelector);
const getViewSelectorTrip = () => getViewSelector().findComponent(GlAlert);
const getLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
@@ -95,15 +89,11 @@ describe('Pipeline graph wrapper', () => {
const callouts = mapCallouts(calloutsList);
const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse(callouts));
const getPipelineHeaderDataHandler = jest.fn().mockResolvedValue(mockRunningPipelineHeaderData);
- const getPerformanceInsightsHandler = jest
- .fn()
- .mockResolvedValue(mockPerformanceInsightsResponse);
const requestHandlers = [
[getPipelineHeaderData, getPipelineHeaderDataHandler],
[getPipelineDetails, getPipelineDetailsHandler],
[getUserCallouts, getUserCalloutsHandler],
- [getPerformanceInsights, getPerformanceInsightsHandler],
];
const apolloProvider = createMockApollo(requestHandlers);
@@ -309,7 +299,7 @@ describe('Pipeline graph wrapper', () => {
const groupsInFirstColumn =
mockPipelineResponse.data.project.pipeline.stages.nodes[0].groups.nodes.length;
expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn);
- expect(getStageColumnTitle().text()).toBe('Build');
+ expect(getStageColumnTitle().text()).toBe('build');
await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1);
expect(getStageColumnTitle().text()).toBe('');
@@ -418,7 +408,7 @@ describe('Pipeline graph wrapper', () => {
it('reads the view type from localStorage when available', () => {
const viewSelectorNeedsSegment = wrapper
- .find(GlButtonGroup)
+ .findComponent(GlButtonGroup)
.findAllComponents(GlButton)
.at(1);
expect(viewSelectorNeedsSegment.classes()).toContain('selected');
@@ -564,7 +554,7 @@ describe('Pipeline graph wrapper', () => {
mock.restore();
});
- it('it calls reportPerformance with expected arguments', () => {
+ it('calls reportPerformance with expected arguments', () => {
expect(markAndMeasure).toHaveBeenCalled();
expect(reportPerformance).toHaveBeenCalled();
expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData);
diff --git a/spec/frontend/pipelines/graph/graph_view_selector_spec.js b/spec/frontend/pipelines/graph/graph_view_selector_spec.js
index 1397500bdc7..43587bebedf 100644
--- a/spec/frontend/pipelines/graph/graph_view_selector_spec.js
+++ b/spec/frontend/pipelines/graph/graph_view_selector_spec.js
@@ -1,34 +1,23 @@
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants';
import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql';
-import { mockPerformanceInsightsResponse } from './mock_data';
-
-Vue.use(VueApollo);
describe('the graph view selector component', () => {
let wrapper;
- let trackingSpy;
const findDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]');
const findViewTypeSelector = () => wrapper.findComponent(GlButtonGroup);
const findStageViewButton = () => findViewTypeSelector().findAllComponents(GlButton).at(0);
const findLayerViewButton = () => findViewTypeSelector().findAllComponents(GlButton).at(1);
const findSwitcherLoader = () => wrapper.find('[data-testid="switcher-loading-state"]');
- const findToggleLoader = () => findDependenciesToggle().find(GlLoadingIcon);
+ const findToggleLoader = () => findDependenciesToggle().findComponent(GlLoadingIcon);
const findHoverTip = () => wrapper.findComponent(GlAlert);
- const findPipelineInsightsBtn = () => wrapper.find('[data-testid="pipeline-insights-btn"]');
const defaultProps = {
showLinks: false,
tipPreviouslyDismissed: false,
type: STAGE_VIEW,
- isPipelineComplete: true,
};
const defaultData = {
@@ -38,14 +27,6 @@ describe('the graph view selector component', () => {
showLinksActive: false,
};
- const getPerformanceInsightsHandler = jest
- .fn()
- .mockResolvedValue(mockPerformanceInsightsResponse);
-
- const requestHandlers = [[getPerformanceInsights, getPerformanceInsightsHandler]];
-
- const apolloProvider = createMockApollo(requestHandlers);
-
const createComponent = ({ data = {}, mountFn = shallowMount, props = {} } = {}) => {
wrapper = mountFn(GraphViewSelector, {
propsData: {
@@ -58,7 +39,6 @@ describe('the graph view selector component', () => {
...data,
};
},
- apolloProvider,
});
};
@@ -222,44 +202,5 @@ describe('the graph view selector component', () => {
expect(findHoverTip().exists()).toBe(false);
});
});
-
- describe('pipeline insights', () => {
- it.each`
- isPipelineComplete | shouldShow
- ${true} | ${true}
- ${false} | ${false}
- `(
- 'button should display $shouldShow if isPipelineComplete is $isPipelineComplete ',
- ({ isPipelineComplete, shouldShow }) => {
- createComponent({
- props: {
- isPipelineComplete,
- },
- });
-
- expect(findPipelineInsightsBtn().exists()).toBe(shouldShow);
- },
- );
- });
-
- describe('tracking', () => {
- beforeEach(() => {
- createComponent();
-
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- it('tracks performance insights button click', () => {
- findPipelineInsightsBtn().vm.$emit('click');
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_insights_button', {
- label: 'performance_insights',
- });
- });
- });
});
});
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 4f0da09fec6..05776ec0706 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -59,7 +59,7 @@ describe('pipeline graph job item', () => {
});
});
- it('it should render status and name', () => {
+ it('should render status and name', () => {
expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
expect(wrapper.find('a').exists()).toBe(false);
@@ -72,7 +72,7 @@ describe('pipeline graph job item', () => {
});
describe('action icon', () => {
- it('it should render the action icon', () => {
+ it('should render the action icon', () => {
createWrapper({ job: mockJob });
const actionComponent = findActionComponent();
@@ -82,7 +82,7 @@ describe('pipeline graph job item', () => {
expect(actionComponent.attributes('disabled')).not.toBe('disabled');
});
- it('it should render disabled action icon when user cannot run the action', () => {
+ it('should render disabled action icon when user cannot run the action', () => {
createWrapper({ job: mockJobWithUnauthorizedAction });
const actionComponent = findActionComponent();
diff --git a/spec/frontend/pipelines/graph/job_name_component_spec.js b/spec/frontend/pipelines/graph/job_name_component_spec.js
index d3008c046e8..ec432e98fdf 100644
--- a/spec/frontend/pipelines/graph/job_name_component_spec.js
+++ b/spec/frontend/pipelines/graph/job_name_component_spec.js
@@ -24,7 +24,7 @@ describe('job name component', () => {
});
it('should render an icon with the provided status', () => {
- expect(wrapper.find(ciIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(ciIcon).exists()).toBe(true);
expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index 7d1e4774a24..399d52c3dff 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -36,13 +36,13 @@ describe('Linked pipeline', () => {
type: UPSTREAM,
};
- const findButton = () => wrapper.find(GlButton);
+ const findButton = () => wrapper.findComponent(GlButton);
const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline');
const findCardTooltip = () => wrapper.findComponent(GlTooltip);
const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title');
const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button');
- const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findLinkedPipeline = () => wrapper.findComponent({ ref: 'linkedPipeline' });
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label');
const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
@@ -80,7 +80,7 @@ describe('Linked pipeline', () => {
});
it('should render an svg within the status container', () => {
- const pipelineStatusElement = wrapper.find(CiStatus);
+ const pipelineStatusElement = wrapper.findComponent(CiStatus);
expect(pipelineStatusElement.find('svg').exists()).toBe(true);
});
@@ -90,7 +90,7 @@ describe('Linked pipeline', () => {
});
it('should have a ci-status child component', () => {
- expect(wrapper.find(CiStatus).exists()).toBe(true);
+ expect(wrapper.findComponent(CiStatus).exists()).toBe(true);
});
it('should render the pipeline id', () => {
@@ -214,7 +214,7 @@ describe('Linked pipeline', () => {
await findRetryButton().trigger('click');
});
- it('calls the retry mutation ', () => {
+ it('calls the retry mutation', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: RetryPipelineMutation,
@@ -255,7 +255,7 @@ describe('Linked pipeline', () => {
createWrapper({ propsData: cancelablePipeline });
});
- it('shows only the cancel button ', () => {
+ it('shows only the cancel button', () => {
expect(findCancelButton().exists()).toBe(true);
expect(findRetryButton().exists()).toBe(false);
});
@@ -375,7 +375,7 @@ describe('Linked pipeline', () => {
${'mouseover'} | ${'mouseout'}
${'focus'} | ${'blur'}
`(
- 'applies the class on $activateEventName and removes it on $deactivateEventName ',
+ 'applies the class on $activateEventName and removes it on $deactivateEventName',
async ({ activateEventName, deactivateEventName }) => {
const shadowClass = 'gl-shadow-none!';
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 46000711110..63e2d8707ea 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -38,8 +38,8 @@ describe('Linked Pipelines Column', () => {
let wrapper;
const findLinkedColumnTitle = () => wrapper.find('[data-testid="linked-column-title"]');
- const findLinkedPipelineElements = () => wrapper.findAll(LinkedPipeline);
- const findPipelineGraph = () => wrapper.find(PipelineGraph);
+ const findLinkedPipelineElements = () => wrapper.findAllComponents(LinkedPipeline);
+ const findPipelineGraph = () => wrapper.findComponent(PipelineGraph);
const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]');
Vue.use(VueApollo);
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index 959bbcefc98..6124d67af09 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -1038,245 +1038,3 @@ export const triggerJob = {
action: null,
},
};
-
-export const mockPerformanceInsightsResponse = {
- data: {
- project: {
- __typename: 'Project',
- id: 'gid://gitlab/Project/20',
- pipeline: {
- __typename: 'Pipeline',
- id: 'gid://gitlab/Ci::Pipeline/97',
- jobs: {
- __typename: 'CiJobConnection',
- pageInfo: {
- __typename: 'PageInfo',
- hasNextPage: false,
- },
- nodes: [
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Bridge/2502',
- duration: null,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2502-2502',
- detailsPath: '/root/lots-of-jobs-project/-/pipelines/98',
- },
- name: 'trigger_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/303',
- name: 'deploy',
- },
- startedAt: null,
- queuedDuration: 424850.376278,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2501',
- duration: 10,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2501-2501',
- detailsPath: '/root/ci-project/-/jobs/2501',
- },
- name: 'artifact_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/303',
- name: 'deploy',
- },
- startedAt: '2022-07-01T16:31:41Z',
- queuedDuration: 2.621553,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2500',
- duration: 4,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2500-2500',
- detailsPath: '/root/ci-project/-/jobs/2500',
- },
- name: 'coverage_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/302',
- name: 'test',
- },
- startedAt: '2022-07-01T16:31:33Z',
- queuedDuration: 14.388869,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2499',
- duration: 4,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2499-2499',
- detailsPath: '/root/ci-project/-/jobs/2499',
- },
- name: 'test_job_two',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/302',
- name: 'test',
- },
- startedAt: '2022-07-01T16:31:28Z',
- queuedDuration: 15.792664,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2498',
- duration: 4,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2498-2498',
- detailsPath: '/root/ci-project/-/jobs/2498',
- },
- name: 'test_job_one',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/302',
- name: 'test',
- },
- startedAt: '2022-07-01T16:31:17Z',
- queuedDuration: 8.317072,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2497',
- duration: 5,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'failed-2497-2497',
- detailsPath: '/root/ci-project/-/jobs/2497',
- },
- name: 'allow_failure_test_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/302',
- name: 'test',
- },
- startedAt: '2022-07-01T16:31:22Z',
- queuedDuration: 3.547553,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2496',
- duration: null,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'manual-2496-2496',
- detailsPath: '/root/ci-project/-/jobs/2496',
- },
- name: 'test_manual_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/302',
- name: 'test',
- },
- startedAt: null,
- queuedDuration: null,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2495',
- duration: 5,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2495-2495',
- detailsPath: '/root/ci-project/-/jobs/2495',
- },
- name: 'large_log_output',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/301',
- name: 'build',
- },
- startedAt: '2022-07-01T16:31:11Z',
- queuedDuration: 79.128625,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2494',
- duration: 5,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2494-2494',
- detailsPath: '/root/ci-project/-/jobs/2494',
- },
- name: 'build_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/301',
- name: 'build',
- },
- startedAt: '2022-07-01T16:31:05Z',
- queuedDuration: 73.286895,
- },
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Build/2493',
- duration: 16,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2493-2493',
- detailsPath: '/root/ci-project/-/jobs/2493',
- },
- name: 'wait_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/301',
- name: 'build',
- },
- startedAt: '2022-07-01T16:30:48Z',
- queuedDuration: 56.258856,
- },
- ],
- },
- },
- },
- },
-};
-
-export const mockPerformanceInsightsNextPageResponse = {
- data: {
- project: {
- __typename: 'Project',
- id: 'gid://gitlab/Project/20',
- pipeline: {
- __typename: 'Pipeline',
- id: 'gid://gitlab/Ci::Pipeline/97',
- jobs: {
- __typename: 'CiJobConnection',
- pageInfo: {
- __typename: 'PageInfo',
- hasNextPage: true,
- },
- nodes: [
- {
- __typename: 'CiJob',
- id: 'gid://gitlab/Ci::Bridge/2502',
- duration: null,
- detailedStatus: {
- __typename: 'DetailedStatus',
- id: 'success-2502-2502',
- detailsPath: '/root/lots-of-jobs-project/-/pipelines/98',
- },
- name: 'trigger_job',
- stage: {
- __typename: 'CiStage',
- id: 'gid://gitlab/Ci::Stage/303',
- name: 'deploy',
- },
- startedAt: null,
- queuedDuration: 424850.376278,
- },
- ],
- },
- },
- },
- },
-};
diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js
index 99e8ea9d0a4..19f597a7267 100644
--- a/spec/frontend/pipelines/graph/stage_column_component_spec.js
+++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js
@@ -42,8 +42,8 @@ describe('stage column component', () => {
const findStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]');
const findStageColumnGroup = () => wrapper.find('[data-testid="stage-column-group"]');
const findAllStageColumnGroups = () => wrapper.findAll('[data-testid="stage-column-group"]');
- const findJobItem = () => wrapper.find(JobItem);
- const findActionComponent = () => wrapper.find(ActionComponent);
+ const findJobItem = () => wrapper.findComponent(JobItem);
+ const findActionComponent = () => wrapper.findComponent(ActionComponent);
const createComponent = ({ method = shallowMount, props = {} } = {}) => {
wrapper = method(StageColumnComponent, {
@@ -126,9 +126,9 @@ describe('stage column component', () => {
});
});
- it('capitalizes and escapes name', () => {
- expect(findStageColumnTitle().text()).toBe(
- 'Test &lt;img src=x onerror=alert(document.domain)&gt;',
+ it('escapes name', () => {
+ expect(findStageColumnTitle().html()).toContain(
+ 'test &lt;img src=x onerror=alert(document.domain)&gt;',
);
});
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index 44ab60cbee7..e2699d6ff2e 100644
--- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -6,7 +6,7 @@ import { generateResponse, mockPipelineResponse } from '../graph/mock_data';
describe('links layer component', () => {
let wrapper;
- const findLinksInner = () => wrapper.find(LinksInner);
+ const findLinksInner = () => wrapper.findComponent(LinksInner);
const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
const containerId = `pipeline-links-container-${pipeline.id}`;
diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js
index 859be8d342c..e583c0798f5 100644
--- a/spec/frontend/pipelines/header_component_spec.js
+++ b/spec/frontend/pipelines/header_component_spec.js
@@ -21,12 +21,12 @@ describe('Pipeline details header', () => {
let glModalDirective;
let mutate = jest.fn();
- const findAlert = () => wrapper.find(GlAlert);
- const findDeleteModal = () => wrapper.find(GlModal);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findDeleteModal = () => wrapper.findComponent(GlModal);
const findRetryButton = () => wrapper.find('[data-testid="retryPipeline"]');
const findCancelButton = () => wrapper.find('[data-testid="cancelPipeline"]');
const findDeleteButton = () => wrapper.find('[data-testid="deletePipeline"]');
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const defaultProvideOptions = {
pipelineId: '14',
diff --git a/spec/frontend/pipelines/performance_insights_modal_spec.js b/spec/frontend/pipelines/performance_insights_modal_spec.js
deleted file mode 100644
index 8c802be7718..00000000000
--- a/spec/frontend/pipelines/performance_insights_modal_spec.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import { GlAlert, GlLink, GlModal } from '@gitlab/ui';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import waitForPromises from 'helpers/wait_for_promises';
-import PerformanceInsightsModal from '~/pipelines/components/performance_insights_modal.vue';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { trimText } from 'helpers/text_helper';
-import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql';
-import {
- mockPerformanceInsightsResponse,
- mockPerformanceInsightsNextPageResponse,
-} from './graph/mock_data';
-
-Vue.use(VueApollo);
-
-describe('Performance insights modal', () => {
- let wrapper;
-
- const findModal = () => wrapper.findComponent(GlModal);
- const findAlert = () => wrapper.findComponent(GlAlert);
- const findLink = () => wrapper.findComponent(GlLink);
- const findLimitText = () => wrapper.findByTestId('limit-alert-text');
- const findQueuedCardData = () => wrapper.findByTestId('insights-queued-card-data');
- const findQueuedCardLink = () => wrapper.findByTestId('insights-queued-card-link');
- const findExecutedCardData = () => wrapper.findByTestId('insights-executed-card-data');
- const findExecutedCardLink = () => wrapper.findByTestId('insights-executed-card-link');
- const findSlowJobsStage = (index) => wrapper.findAllByTestId('insights-slow-job-stage').at(index);
- const findSlowJobsLink = (index) => wrapper.findAllByTestId('insights-slow-job-link').at(index);
-
- const getPerformanceInsightsHandler = jest
- .fn()
- .mockResolvedValue(mockPerformanceInsightsResponse);
-
- const getPerformanceInsightsNextPageHandler = jest
- .fn()
- .mockResolvedValue(mockPerformanceInsightsNextPageResponse);
-
- const requestHandlers = [[getPerformanceInsights, getPerformanceInsightsHandler]];
-
- const createComponent = (handlers = requestHandlers) => {
- wrapper = shallowMountExtended(PerformanceInsightsModal, {
- provide: {
- pipelineIid: '1',
- pipelineProjectPath: 'root/ci-project',
- },
- apolloProvider: createMockApollo(handlers),
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('without next page', () => {
- beforeEach(async () => {
- createComponent();
-
- await waitForPromises();
- });
-
- it('displays modal', () => {
- expect(findModal().exists()).toBe(true);
- });
-
- it('displays alert', () => {
- expect(findAlert().exists()).toBe(true);
- });
-
- it('displays feedback issue link', () => {
- expect(findLink().text()).toBe('Feedback issue');
- expect(findLink().attributes('href')).toBe(
- 'https://gitlab.com/gitlab-org/gitlab/-/issues/365902',
- );
- });
-
- it('does not display limit text', () => {
- expect(findLimitText().exists()).toBe(false);
- });
-
- describe('queued duration card', () => {
- it('displays card data', () => {
- expect(trimText(findQueuedCardData().text())).toBe('4.9 days');
- });
- it('displays card link', () => {
- expect(findQueuedCardLink().attributes('href')).toBe(
- '/root/lots-of-jobs-project/-/pipelines/98',
- );
- });
- });
-
- describe('executed duration card', () => {
- it('displays card data', () => {
- expect(trimText(findExecutedCardData().text())).toBe('trigger_job');
- });
- it('displays card link', () => {
- expect(findExecutedCardLink().attributes('href')).toBe(
- '/root/lots-of-jobs-project/-/pipelines/98',
- );
- });
- });
-
- describe('slow jobs', () => {
- it.each`
- index | expectedStage | expectedName | expectedLink
- ${0} | ${'build'} | ${'wait_job'} | ${'/root/ci-project/-/jobs/2493'}
- ${1} | ${'deploy'} | ${'artifact_job'} | ${'/root/ci-project/-/jobs/2501'}
- ${2} | ${'test'} | ${'allow_failure_test_job'} | ${'/root/ci-project/-/jobs/2497'}
- ${3} | ${'build'} | ${'large_log_output'} | ${'/root/ci-project/-/jobs/2495'}
- ${4} | ${'build'} | ${'build_job'} | ${'/root/ci-project/-/jobs/2494'}
- `(
- 'should display slow job correctly',
- ({ index, expectedStage, expectedName, expectedLink }) => {
- expect(findSlowJobsStage(index).text()).toBe(expectedStage);
- expect(findSlowJobsLink(index).text()).toBe(expectedName);
- expect(findSlowJobsLink(index).attributes('href')).toBe(expectedLink);
- },
- );
- });
- });
-
- describe('with next page', () => {
- it('displays limit text when there is a next page', async () => {
- createComponent([[getPerformanceInsights, getPerformanceInsightsNextPageHandler]]);
-
- await waitForPromises();
-
- expect(findLimitText().exists()).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
index 1b89e322d31..d9199f3b0f7 100644
--- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
@@ -34,7 +34,7 @@ describe('pipeline graph component', () => {
};
const findAlert = () => wrapper.findComponent(GlAlert);
- const findAllJobPills = () => wrapper.findAll(JobPill);
+ const findAllJobPills = () => wrapper.findAllComponents(JobPill);
const findAllStageNames = () => wrapper.findAllComponents(StageName);
const findLinksLayer = () => wrapper.findComponent(LinksLayer);
const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]');
diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
index f554166da33..149b40330e2 100644
--- a/spec/frontend/pipelines/pipeline_multi_actions_spec.js
+++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
@@ -1,12 +1,14 @@
import { GlAlert, GlDropdown, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import PipelineMultiActions, {
i18n,
} from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue';
+import { TRACKING_CATEGORIES } from '~/pipelines/constants';
describe('Pipeline Multi Actions Dropdown', () => {
let wrapper;
@@ -136,4 +138,22 @@ describe('Pipeline Multi Actions Dropdown', () => {
});
});
});
+
+ describe('tracking', () => {
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks artifacts dropdown click', () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ createComponent();
+
+ findDropdown().vm.$emit('show');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_artifacts_dropdown', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 25a97ecf49d..1d66607e72b 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,12 +1,15 @@
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import { TRACKING_CATEGORIES } from '~/pipelines/constants';
import { mockPipeline, mockPipelineBranch, mockPipelineTag } from './mock_data';
const projectPath = 'test/test';
describe('Pipeline Url Component', () => {
let wrapper;
+ let trackingSpy;
const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
@@ -14,6 +17,7 @@ describe('Pipeline Url Component', () => {
const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
const findCommitIcon = () => wrapper.findByTestId('commit-icon');
const findCommitIconType = () => wrapper.findByTestId('commit-icon-type');
+ const findCommitRefName = () => wrapper.findByTestId('commit-ref-name');
const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
@@ -31,7 +35,6 @@ describe('Pipeline Url Component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
it('should render pipeline url table cell', () => {
@@ -49,7 +52,7 @@ describe('Pipeline Url Component', () => {
});
it('should render the commit title, commit reference and commit-short-sha', () => {
- createComponent({}, true);
+ createComponent();
const commitWrapper = findCommitTitleContainer();
@@ -83,7 +86,7 @@ describe('Pipeline Url Component', () => {
});
it('should render commit icon tooltip', () => {
- createComponent({}, true);
+ createComponent();
expect(findCommitIcon().attributes('title')).toBe('Commit');
});
@@ -94,8 +97,68 @@ describe('Pipeline Url Component', () => {
${mockPipelineBranch()} | ${'Branch'}
${mockPipeline()} | ${'Merge Request'}
`('should render tooltip $expectedTitle for commit icon type', ({ pipeline, expectedTitle }) => {
- createComponent(pipeline, true);
+ createComponent(pipeline);
expect(findCommitIconType().attributes('title')).toBe(expectedTitle);
});
+
+ describe('tracking', () => {
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks pipeline id click', () => {
+ createComponent();
+
+ findPipelineUrlLink().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_pipeline_id', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks merge request ref click', () => {
+ createComponent();
+
+ findRefName().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_mr_ref', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks commit ref name click', () => {
+ createComponent(mockPipelineBranch());
+
+ findCommitRefName().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_name', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks commit title click', () => {
+ createComponent(mockPipelineBranch());
+
+ findCommitTitle(findCommitTitleContainer()).vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_title', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks commit short sha click', () => {
+ createComponent(mockPipelineBranch());
+
+ findCommitShortSha().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_sha', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index 9b2ee6b8278..fdfced38dca 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -2,6 +2,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
import createFlash from '~/flash';
@@ -9,6 +10,7 @@ import axios from '~/lib/utils/axios_utils';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
+import { TRACKING_CATEGORIES } from '~/pipelines/constants';
jest.mock('~/flash');
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => {
@@ -29,9 +31,9 @@ describe('Pipelines Actions dropdown', () => {
});
};
- const findDropdown = () => wrapper.find(GlDropdown);
- const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
- const findAllCountdowns = () => wrapper.findAll(GlCountdown);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findAllCountdowns = () => wrapper.findAllComponents(GlCountdown);
beforeEach(() => {
mock = new MockAdapter(axios);
@@ -96,6 +98,22 @@ describe('Pipelines Actions dropdown', () => {
expect(createFlash).toHaveBeenCalledTimes(1);
});
});
+
+ describe('tracking', () => {
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks manual actions click', () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ findDropdown().vm.$emit('shown');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_manual_actions', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+ });
});
describe('scheduled jobs', () => {
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index 2d876841e06..e3e54716a7b 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -30,8 +30,9 @@ describe('Pipelines Artifacts dropdown', () => {
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findFirstGlDropdownItem = () => wrapper.find(GlDropdownItem);
- const findAllGlDropdownItems = () => wrapper.find(GlDropdown).findAll(GlDropdownItem);
+ const findFirstGlDropdownItem = () => wrapper.findComponent(GlDropdownItem);
+ const findAllGlDropdownItems = () =>
+ wrapper.findComponent(GlDropdown).findAllComponents(GlDropdownItem);
afterEach(() => {
wrapper.destroy();
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 0bed24e588e..cc2ff90de57 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -7,6 +7,7 @@ import { nextTick } from 'vue';
import mockPipelinesResponse from 'test_fixtures/pipelines/pipelines.json';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import { mockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
@@ -16,7 +17,7 @@ import NavigationControls from '~/pipelines/components/pipelines_list/nav_contro
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
-import { RAW_TEXT_WARNING } from '~/pipelines/constants';
+import { RAW_TEXT_WARNING, TRACKING_CATEGORIES } 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';
@@ -37,6 +38,7 @@ const mockPipelineWithStages = mockPipelinesResponse.pipelines.find(
describe('Pipelines', () => {
let wrapper;
let mock;
+ let trackingSpy;
const paths = {
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
@@ -123,7 +125,7 @@ describe('Pipelines', () => {
});
it('shows loading state when the app is loading', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('does not display tabs when the first request has not yet been made', () => {
@@ -236,6 +238,8 @@ describe('Pipelines', () => {
count: mockPipelinesResponse.count,
});
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
goToTab('finished');
await waitForPromises();
@@ -256,6 +260,12 @@ describe('Pipelines', () => {
`${window.location.pathname}?scope=finished&page=1`,
);
});
+
+ it('tracks tab change click', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_filter_tabs', {
+ label: TRACKING_CATEGORIES.tabs,
+ });
+ });
});
describe('when the scope in the tab is empty', () => {
@@ -375,7 +385,7 @@ describe('Pipelines', () => {
const [firstPage, secondPage] = chunk(mockPipelinesResponse.pipelines, mockPageSize);
const goToPage = (page) => {
- findTablePagination().find(GlPagination).vm.$emit('input', page);
+ findTablePagination().findComponent(GlPagination).vm.$emit('input', page);
};
beforeEach(async () => {
@@ -583,7 +593,7 @@ describe('Pipelines', () => {
'This project is not currently set up to run pipelines.',
);
- expect(findEmptyState().find(GlButton).exists()).toBe(false);
+ expect(findEmptyState().findComponent(GlButton).exists()).toBe(false);
});
it('does not render tabs or buttons', () => {
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 7b49baa5a20..044683ce533 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -2,8 +2,9 @@ import '~/commons';
import { GlTableLite } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import fixture from 'test_fixtures/pipelines/pipelines.json';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue';
import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue';
@@ -13,6 +14,7 @@ import {
PipelineKeyOptions,
BUTTON_TOOLTIP_RETRY,
BUTTON_TOOLTIP_CANCEL,
+ TRACKING_CATEGORIES,
} from '~/pipelines/constants';
import eventHub from '~/pipelines/event_hub';
@@ -23,6 +25,7 @@ jest.mock('~/pipelines/event_hub');
describe('Pipelines Table', () => {
let pipeline;
let wrapper;
+ let trackingSpy;
const defaultProps = {
pipelines: [],
@@ -69,6 +72,7 @@ describe('Pipelines Table', () => {
afterEach(() => {
wrapper.destroy();
+
wrapper = null;
});
@@ -96,10 +100,6 @@ describe('Pipelines Table', () => {
it('should render a status badge', () => {
expect(findStatusBadge().exists()).toBe(true);
});
-
- it('should render status badge with correct path', () => {
- expect(findStatusBadge().attributes('href')).toBe(pipeline.path);
- });
});
describe('pipeline cell', () => {
@@ -113,40 +113,28 @@ describe('Pipelines Table', () => {
});
describe('stages cell', () => {
- it('should render a pipeline mini graph', () => {
+ it('should render pipeline mini graph', () => {
expect(findPipelineMiniGraph().exists()).toBe(true);
});
it('should render the right number of stages', () => {
const stagesLength = pipeline.details.stages.length;
- expect(
- findPipelineMiniGraph().findAll('[data-testid="mini-pipeline-graph-dropdown"]'),
- ).toHaveLength(stagesLength);
+ expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength);
});
describe('when pipeline does not have stages', () => {
beforeEach(() => {
pipeline = createMockPipeline();
- pipeline.details.stages = null;
+ pipeline.details.stages = [];
createComponent({ pipelines: [pipeline] });
});
it('stages are not rendered', () => {
- expect(findPipelineMiniGraph().exists()).toBe(false);
+ expect(findPipelineMiniGraph().props('stages')).toHaveLength(0);
});
});
- it('should not update dropdown', () => {
- expect(findPipelineMiniGraph().props('updateDropdown')).toBe(false);
- });
-
- it('when update graph dropdown is set, should update graph dropdown', () => {
- createComponent({ pipelines: [pipeline], updateGraphDropdown: true });
-
- expect(findPipelineMiniGraph().props('updateDropdown')).toBe(true);
- });
-
it('when action request is complete, should refresh table', () => {
findPipelineMiniGraph().vm.$emit('pipelineActionRequestComplete');
@@ -179,5 +167,47 @@ describe('Pipelines Table', () => {
expect(findTriggerer().exists()).toBe(true);
});
});
+
+ describe('tracking', () => {
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks status badge click', () => {
+ findStatusBadge().vm.$emit('ciStatusBadgeClick');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_ci_status_badge', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks retry pipeline button click', () => {
+ findRetryBtn().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_retry_button', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks cancel pipeline button click', () => {
+ findCancelBtn().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_cancel_button', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+
+ it('tracks pipeline mini graph stage click', () => {
+ findPipelineMiniGraph().vm.$emit('miniGraphStageClick');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_minigraph', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+ });
});
});
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 c372ac06c35..da13df833e7 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -26,10 +26,10 @@ describe('Test reports suite table', () => {
const noCasesMessage = () => wrapper.findByTestId('no-test-cases');
const artifactsExpiredMessage = () => wrapper.findByTestId('artifacts-expired');
- const artifactsExpiredEmptyState = () => wrapper.find(GlEmptyState);
+ const artifactsExpiredEmptyState = () => wrapper.findComponent(GlEmptyState);
const allCaseRows = () => wrapper.findAllByTestId('test-case-row');
const findCaseRowAtIndex = (index) => wrapper.findAllByTestId('test-case-row').at(index);
- const findLinkForRow = (row) => row.find(GlLink);
+ const findLinkForRow = (row) => row.findComponent(GlLink);
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
const createComponent = ({ suite = testSuite, perPage = 20, errorMessage } = {}) => {
@@ -113,7 +113,7 @@ describe('Test reports suite table', () => {
const filePath = `${blobPath}/${relativeFile}`;
const row = findCaseRowAtIndex(0);
const fileLink = findLinkForRow(row);
- const button = row.find(GlButton);
+ const button = row.findComponent(GlButton);
expect(fileLink.attributes('href')).toBe(filePath);
expect(row.text()).toContain(file);
@@ -134,7 +134,7 @@ describe('Test reports suite table', () => {
});
it('renders a pagination component', () => {
- expect(wrapper.find(GlPagination).exists()).toBe(true);
+ expect(wrapper.findComponent(GlPagination).exists()).toBe(true);
});
});
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 0e1229f7067..cfe9ff564dc 100644
--- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
@@ -44,7 +44,7 @@ describe('Test reports summary table', () => {
describe('when test reports are supplied', () => {
beforeEach(() => createComponent());
- const findErrorIcon = () => wrapper.find({ ref: 'suiteErrorIcon' });
+ const findErrorIcon = () => wrapper.findComponent({ ref: 'suiteErrorIcon' });
it('renders the correct number of rows', () => {
expect(noSuitesToShow().exists()).toBe(false);
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 3de7995b476..f0da0df2ba6 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -48,7 +48,7 @@ describe('Timeago component', () => {
});
it('should render duration and timer svg', () => {
- const icon = duration().find(GlIcon);
+ const icon = duration().findComponent(GlIcon);
expect(duration().exists()).toBe(true);
expect(icon.props('name')).toBe('timer');
@@ -71,7 +71,7 @@ describe('Timeago component', () => {
});
it('should render time and calendar icon', () => {
- const icon = finishedAt().find(GlIcon);
+ const icon = finishedAt().findComponent(GlIcon);
const time = finishedAt().find('time');
expect(finishedAt().exists()).toBe(true);
diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
index ba478363d04..caa66502e11 100644
--- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
@@ -9,9 +9,10 @@ import { branches, mockBranchesAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
let wrapper;
- const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
- const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+ const findAllFilteredSearchSuggestions = () =>
+ wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const getBranchSuggestions = () =>
findAllFilteredSearchSuggestions().wrappers.map((w) => w.text());
diff --git a/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js
index b8abf2c1727..60abb63a7e0 100644
--- a/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js
@@ -7,8 +7,9 @@ import PipelineSourceToken from '~/pipelines/components/pipelines_list/tokens/pi
describe('Pipeline Source Token', () => {
let wrapper;
- const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
- const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
+ const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+ const findAllFilteredSearchSuggestions = () =>
+ wrapper.findAllComponents(GlFilteredSearchSuggestion);
const defaultProps = {
config: {
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index 2c5fa8b00e2..94f9a37f707 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -6,9 +6,10 @@ import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pi
describe('Pipeline Status Token', () => {
let wrapper;
- const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
- const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
- const findAllGlIcons = () => wrapper.findAll(GlIcon);
+ const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+ const findAllFilteredSearchSuggestions = () =>
+ wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const findAllGlIcons = () => wrapper.findAllComponents(GlIcon);
const defaultProps = {
config: {
diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
index 596a9218c39..7311a5d2f5a 100644
--- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
@@ -7,9 +7,10 @@ import { tags, mockTagsAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
let wrapper;
- const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
- const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+ const findAllFilteredSearchSuggestions = () =>
+ wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const stubs = {
GlFilteredSearchToken: {
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 397dbdf95a9..c763bfe1b27 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -8,9 +8,10 @@ import { users } from '../mock_data';
describe('Pipeline Trigger Author Token', () => {
let wrapper;
- const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
- const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+ const findAllFilteredSearchSuggestions = () =>
+ wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const defaultProps = {
config: {
diff --git a/spec/frontend/pipelines/utils_spec.js b/spec/frontend/pipelines/utils_spec.js
index a82390fae22..1c23a7e4fcf 100644
--- a/spec/frontend/pipelines/utils_spec.js
+++ b/spec/frontend/pipelines/utils_spec.js
@@ -8,14 +8,10 @@ import {
removeOrphanNodes,
getMaxNodes,
} from '~/pipelines/components/parsing_utils';
-import { createNodeDict, calculateJobStats, calculateSlowestFiveJobs } from '~/pipelines/utils';
+import { createNodeDict } from '~/pipelines/utils';
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
-import {
- generateResponse,
- mockPipelineResponse,
- mockPerformanceInsightsResponse,
-} from './graph/mock_data';
+import { generateResponse, mockPipelineResponse } from './graph/mock_data';
describe('DAG visualization parsing utilities', () => {
const nodeDict = createNodeDict(mockParsedGraphQLNodes);
@@ -162,40 +158,4 @@ describe('DAG visualization parsing utilities', () => {
expect(columns).toMatchSnapshot();
});
});
-
- describe('performance insights', () => {
- const {
- data: {
- project: {
- pipeline: { jobs },
- },
- },
- } = mockPerformanceInsightsResponse;
-
- describe('calculateJobStats', () => {
- const expectedJob = jobs.nodes[0];
-
- it('returns the job that spent this longest time queued', () => {
- expect(calculateJobStats(jobs, 'queuedDuration')).toEqual(expectedJob);
- });
-
- it('returns the job that was executed last', () => {
- expect(calculateJobStats(jobs, 'startedAt')).toEqual(expectedJob);
- });
- });
-
- describe('calculateSlowestFiveJobs', () => {
- it('returns the slowest five jobs of the pipeline', () => {
- const expectedJobs = [
- jobs.nodes[9],
- jobs.nodes[1],
- jobs.nodes[5],
- jobs.nodes[7],
- jobs.nodes[8],
- ];
-
- expect(calculateSlowestFiveJobs(jobs)).toEqual(expectedJobs);
- });
- });
- });
});
diff --git a/spec/frontend/popovers/components/popovers_spec.js b/spec/frontend/popovers/components/popovers_spec.js
index 6fdcd34ae83..eba6b95214d 100644
--- a/spec/frontend/popovers/components/popovers_spec.js
+++ b/spec/frontend/popovers/components/popovers_spec.js
@@ -31,7 +31,7 @@ describe('popovers/components/popovers.vue', () => {
return target;
};
- const allPopovers = () => wrapper.findAll(GlPopover);
+ const allPopovers = () => wrapper.findAllComponents(GlPopover);
afterEach(() => {
wrapper.destroy();
@@ -42,7 +42,7 @@ describe('popovers/components/popovers.vue', () => {
it('attaches popovers to the targets specified', async () => {
const target = createPopoverTarget();
await buildWrapper(target);
- expect(wrapper.find(GlPopover).props('target')).toBe(target);
+ expect(wrapper.findComponent(GlPopover).props('target')).toBe(target);
});
it('does not attach a popover twice to the same element', async () => {
@@ -52,7 +52,7 @@ describe('popovers/components/popovers.vue', () => {
await nextTick();
- expect(wrapper.findAll(GlPopover)).toHaveLength(1);
+ expect(wrapper.findAllComponents(GlPopover)).toHaveLength(1);
});
describe('supports HTML content', () => {
@@ -66,7 +66,7 @@ describe('popovers/components/popovers.vue', () => {
`('$description', async ({ content, render }) => {
await buildWrapper(createPopoverTarget({ content, html: true }));
- const html = wrapper.find(GlPopover).html();
+ const html = wrapper.findComponent(GlPopover).html();
expect(html).toContain(render);
});
});
@@ -78,7 +78,7 @@ describe('popovers/components/popovers.vue', () => {
`('sets $option to $value when data-$option is set in target', async ({ option, value }) => {
await buildWrapper(createPopoverTarget({ [option]: value }));
- expect(wrapper.find(GlPopover).props(option)).toBe(value);
+ expect(wrapper.findComponent(GlPopover).props(option)).toBe(value);
});
});
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 ad62d84c43c..e4a316e1ee7 100644
--- a/spec/frontend/profile/account/components/delete_account_modal_spec.js
+++ b/spec/frontend/profile/account/components/delete_account_modal_spec.js
@@ -53,7 +53,7 @@ describe('DeleteAccountModal component', () => {
input: vm.$el.querySelector(`[name="${confirmation}"]`),
};
};
- const findModal = () => wrapper.find(GlModalStub);
+ const findModal = () => wrapper.findComponent(GlModalStub);
describe('with password confirmation', () => {
beforeEach(async () => {
diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js
index 0e56bccf27e..e331eed1863 100644
--- a/spec/frontend/profile/account/components/update_username_spec.js
+++ b/spec/frontend/profile/account/components/update_username_spec.js
@@ -44,7 +44,7 @@ describe('UpdateUsername component', () => {
});
const findElements = () => {
- const modal = wrapper.find(GlModal);
+ const modal = wrapper.findComponent(GlModal);
return {
modal,
@@ -149,7 +149,7 @@ describe('UpdateUsername component', () => {
await expect(wrapper.vm.onConfirm()).rejects.toThrow();
- expect(createFlash).toBeCalledWith({
+ expect(createFlash).toHaveBeenCalledWith({
message: 'Invalid username',
});
});
@@ -161,7 +161,7 @@ describe('UpdateUsername component', () => {
await expect(wrapper.vm.onConfirm()).rejects.toThrow();
- expect(createFlash).toBeCalledWith({
+ expect(createFlash).toHaveBeenCalledWith({
message: 'An error occurred while updating your username, please try again.',
});
});
diff --git a/spec/frontend/profile/preferences/components/integration_view_spec.js b/spec/frontend/profile/preferences/components/integration_view_spec.js
index 92c53b8c91b..f650bee7fda 100644
--- a/spec/frontend/profile/preferences/components/integration_view_spec.js
+++ b/spec/frontend/profile/preferences/components/integration_view_spec.js
@@ -98,6 +98,6 @@ describe('IntegrationView component', () => {
it('should render the help text', () => {
wrapper = createComponent();
- expect(wrapper.find(IntegrationHelpText).exists()).toBe(true);
+ expect(wrapper.findComponent(IntegrationHelpText).exists()).toBe(true);
});
});
diff --git a/spec/frontend/profile/preferences/components/profile_preferences_spec.js b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
index 4d2dcf83d3b..89ce838a383 100644
--- a/spec/frontend/profile/preferences/components/profile_preferences_spec.js
+++ b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
@@ -90,7 +90,7 @@ describe('ProfilePreferences component', () => {
it('should not render Integrations section', () => {
wrapper = createComponent();
- const views = wrapper.findAll(IntegrationView);
+ const views = wrapper.findAllComponents(IntegrationView);
const divider = findIntegrationsDivider();
const heading = findIntegrationsHeading();
@@ -103,7 +103,7 @@ describe('ProfilePreferences component', () => {
wrapper = createComponent({ provide: { integrationViews } });
const divider = findIntegrationsDivider();
const heading = findIntegrationsHeading();
- const views = wrapper.findAll(IntegrationView);
+ const views = wrapper.findAllComponents(IntegrationView);
expect(divider.exists()).toBe(true);
expect(heading.exists()).toBe(true);
diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js
index 79e9dab935d..20c312ec771 100644
--- a/spec/frontend/projects/commit/components/form_modal_spec.js
+++ b/spec/frontend/projects/commit/components/form_modal_spec.js
@@ -99,7 +99,9 @@ describe('CommitFormModal', () => {
createComponent(shallowMount, {}, { prependedText: '_prepended_text_' });
expect(findPrependedText().exists()).toBe(true);
- expect(findPrependedText().find(GlSprintf).attributes('message')).toBe('_prepended_text_');
+ expect(findPrependedText().findComponent(GlSprintf).attributes('message')).toBe(
+ '_prepended_text_',
+ );
});
it('Does not show prepended text', () => {
@@ -124,7 +126,7 @@ describe('CommitFormModal', () => {
createComponent(shallowMount, { pushCode: false });
expect(findAppendedText().exists()).toBe(true);
- expect(findAppendedText().find(GlSprintf).attributes('message')).toContain(
+ expect(findAppendedText().findComponent(GlSprintf).attributes('message')).toContain(
mockData.modalPropsData.i18n.branchInFork,
);
});
@@ -133,7 +135,7 @@ describe('CommitFormModal', () => {
createComponent(shallowMount, { pushCode: false, branchCollaboration: true });
expect(findAppendedText().exists()).toBe(true);
- expect(findAppendedText().find(GlSprintf).attributes('message')).toContain(
+ expect(findAppendedText().findComponent(GlSprintf).attributes('message')).toContain(
mockData.modalPropsData.i18n.existingBranch,
);
});
diff --git a/spec/frontend/projects/commit/store/mutations_spec.js b/spec/frontend/projects/commit/store/mutations_spec.js
index 60abf0fddad..40174b3057a 100644
--- a/spec/frontend/projects/commit/store/mutations_spec.js
+++ b/spec/frontend/projects/commit/store/mutations_spec.js
@@ -26,7 +26,7 @@ describe('Commit form modal mutations', () => {
});
describe('CLEAR_MODAL', () => {
- it('should clear modal state ', () => {
+ it('should clear modal state', () => {
stateCopy = { branch: '_main_', defaultBranch: '_default_branch_' };
mutations[types.CLEAR_MODAL](stateCopy);
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index 57e5ef0ed1d..907e0e226b6 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -58,11 +58,11 @@ describe('Author Select', () => {
resetHTMLFixture();
});
- const findDropdownContainer = () => wrapper.find({ ref: 'dropdownContainer' });
- const findDropdown = () => wrapper.find(GlDropdown);
- const findDropdownHeader = () => wrapper.find(GlDropdownSectionHeader);
- const findSearchBox = () => wrapper.find(GlSearchBoxByType);
- const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+ const findDropdownContainer = () => wrapper.findComponent({ ref: 'dropdownContainer' });
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownHeader = () => wrapper.findComponent(GlDropdownSectionHeader);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
describe('user is searching via "filter by commit message"', () => {
it('disables dropdown container', async () => {
diff --git a/spec/frontend/projects/compare/components/app_spec.js b/spec/frontend/projects/compare/components/app_spec.js
index c9ffdf20c32..2dbecf7cc61 100644
--- a/spec/frontend/projects/compare/components/app_spec.js
+++ b/spec/frontend/projects/compare/components/app_spec.js
@@ -58,7 +58,7 @@ describe('CompareApp component', () => {
});
it('render Source and Target BranchDropdown components', () => {
- const revisionCards = wrapper.findAll(RevisionCard);
+ const revisionCards = wrapper.findAllComponents(RevisionCard);
expect(revisionCards.length).toBe(2);
expect(revisionCards.at(0).props('revisionText')).toBe('Source');
@@ -66,7 +66,7 @@ describe('CompareApp component', () => {
});
describe('compare button', () => {
- const findCompareButton = () => wrapper.find(GlButton);
+ const findCompareButton = () => wrapper.findComponent(GlButton);
it('renders button', () => {
expect(findCompareButton().exists()).toBe(true);
diff --git a/spec/frontend/projects/compare/components/repo_dropdown_spec.js b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
index 98aec347e4b..21cca857c6a 100644
--- a/spec/frontend/projects/compare/components/repo_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
@@ -21,7 +21,7 @@ describe('RepoDropdown component', () => {
wrapper = null;
});
- const findGlDropdown = () => wrapper.find(GlDropdown);
+ const findGlDropdown = () => wrapper.findComponent(GlDropdown);
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
describe('Source Revision', () => {
@@ -73,7 +73,7 @@ describe('RepoDropdown component', () => {
});
it('emits `selectProject` event when another target project is selected', async () => {
- findGlDropdown().findAll(GlDropdownItem).at(0).vm.$emit('click');
+ findGlDropdown().findAllComponents(GlDropdownItem).at(0).vm.$emit('click');
await nextTick();
expect(wrapper.emitted('selectProject')[0][0]).toEqual({
diff --git a/spec/frontend/projects/compare/components/revision_card_spec.js b/spec/frontend/projects/compare/components/revision_card_spec.js
index a741393fcf3..b23bd91ceda 100644
--- a/spec/frontend/projects/compare/components/revision_card_spec.js
+++ b/spec/frontend/projects/compare/components/revision_card_spec.js
@@ -32,10 +32,10 @@ describe('RepoDropdown component', () => {
});
it('renders RepoDropdown component', () => {
- expect(wrapper.findAll(RepoDropdown).exists()).toBe(true);
+ expect(wrapper.findAllComponents(RepoDropdown).exists()).toBe(true);
});
it('renders RevisionDropdown component', () => {
- expect(wrapper.findAll(RevisionDropdown).exists()).toBe(true);
+ expect(wrapper.findAllComponents(RevisionDropdown).exists()).toBe(true);
});
});
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
index 102f95f65da..f64af1aa994 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
@@ -38,7 +38,7 @@ describe('RevisionDropdown component', () => {
axiosMock.restore();
});
- const findGlDropdown = () => wrapper.find(GlDropdown);
+ const findGlDropdown = () => wrapper.findComponent(GlDropdown);
it('sets hidden input', () => {
expect(wrapper.find('input[type="hidden"]').attributes('value')).toBe(
@@ -99,7 +99,7 @@ describe('RevisionDropdown component', () => {
});
it('emits a "selectRevision" event when a revision is selected', async () => {
- const findGlDropdownItems = () => wrapper.findAll(GlDropdownItem);
+ const findGlDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstGlDropdownItem = () => findGlDropdownItems().at(0);
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
index c8a90848492..35e32fd3da0 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
@@ -35,8 +35,8 @@ describe('RevisionDropdown component', () => {
axiosMock.restore();
});
- const findGlDropdown = () => wrapper.find(GlDropdown);
- const findSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findGlDropdown = () => wrapper.findComponent(GlDropdown);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
it('sets hidden input', () => {
createComponent();
@@ -144,7 +144,7 @@ describe('RevisionDropdown component', () => {
wrapper.vm.branches = ['some-branch'];
await nextTick();
- findGlDropdown().findAll(GlDropdownItem).at(0).vm.$emit('click');
+ findGlDropdown().findAllComponents(GlDropdownItem).at(0).vm.$emit('click');
expect(wrapper.emitted('selectRevision')[0][0]).toEqual({
direction: 'to',
diff --git a/spec/frontend/projects/components/project_delete_button_spec.js b/spec/frontend/projects/components/project_delete_button_spec.js
index a3bc4931eb3..49e3218e5bc 100644
--- a/spec/frontend/projects/components/project_delete_button_spec.js
+++ b/spec/frontend/projects/components/project_delete_button_spec.js
@@ -8,7 +8,7 @@ jest.mock('lodash/uniqueId', () => () => 'fakeUniqueId');
describe('Project remove modal', () => {
let wrapper;
- const findSharedDeleteButton = () => wrapper.find(SharedDeleteButton);
+ const findSharedDeleteButton = () => wrapper.findComponent(SharedDeleteButton);
const defaultProps = {
confirmPhrase: 'foo',
diff --git a/spec/frontend/projects/components/shared/delete_button_spec.js b/spec/frontend/projects/components/shared/delete_button_spec.js
index 45c39ee91d8..097b18025a3 100644
--- a/spec/frontend/projects/components/shared/delete_button_spec.js
+++ b/spec/frontend/projects/components/shared/delete_button_spec.js
@@ -11,7 +11,7 @@ describe('Project remove modal', () => {
const findFormElement = () => wrapper.find('form');
const findConfirmButton = () => wrapper.find('.js-modal-action-primary');
const findAuthenticityTokenInput = () => findFormElement().find('input[name=authenticity_token]');
- const findModal = () => wrapper.find(GlModal);
+ const findModal = () => wrapper.findComponent(GlModal);
const findTitle = () => wrapper.find('[data-testid="delete-alert-title"]');
const findAlertBody = () => wrapper.find('[data-testid="delete-alert-body"]');
diff --git a/spec/frontend/projects/details/upload_button_spec.js b/spec/frontend/projects/details/upload_button_spec.js
index d7308963088..50638755260 100644
--- a/spec/frontend/projects/details/upload_button_spec.js
+++ b/spec/frontend/projects/details/upload_button_spec.js
@@ -32,11 +32,11 @@ describe('UploadButton', () => {
});
it('displays an upload button', () => {
- expect(wrapper.find(GlButton).exists()).toBe(true);
+ expect(wrapper.findComponent(GlButton).exists()).toBe(true);
});
it('contains a modal', () => {
- const modal = wrapper.find(UploadBlobModal);
+ const modal = wrapper.findComponent(UploadBlobModal);
expect(modal.exists()).toBe(true);
expect(modal.props('modalId')).toBe(MODAL_ID);
@@ -44,7 +44,7 @@ describe('UploadButton', () => {
describe('when clickinig the upload file button', () => {
beforeEach(() => {
- wrapper.find(GlButton).vm.$emit('click');
+ wrapper.findComponent(GlButton).vm.$emit('click');
});
it('opens the modal', () => {
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
index 7b9011fa3d9..e3aaf760d1e 100644
--- a/spec/frontend/projects/pipelines/charts/components/app_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -47,15 +47,16 @@ describe('ProjectsPipelinesChartsApp', () => {
wrapper.destroy();
});
- const findGlTabs = () => wrapper.find(GlTabs);
- const findAllGlTabs = () => wrapper.findAll(GlTab);
+ const findGlTabs = () => wrapper.findComponent(GlTabs);
+ const findAllGlTabs = () => wrapper.findAllComponents(GlTab);
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
- const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub);
- const findTimeToRestoreServiceCharts = () => wrapper.find(TimeToRestoreServiceChartsStub);
- const findChangeFailureRateCharts = () => wrapper.find(ChangeFailureRateChartsStub);
- const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
- const findPipelineCharts = () => wrapper.find(PipelineCharts);
- const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub);
+ const findLeadTimeCharts = () => wrapper.findComponent(LeadTimeChartsStub);
+ const findTimeToRestoreServiceCharts = () =>
+ wrapper.findComponent(TimeToRestoreServiceChartsStub);
+ const findChangeFailureRateCharts = () => wrapper.findComponent(ChangeFailureRateChartsStub);
+ const findDeploymentFrequencyCharts = () => wrapper.findComponent(DeploymentFrequencyChartsStub);
+ const findPipelineCharts = () => wrapper.findComponent(PipelineCharts);
+ const findProjectQualitySummary = () => wrapper.findComponent(ProjectQualitySummaryStub);
describe('when all charts are available', () => {
beforeEach(() => {
diff --git a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
index 7bb289408b8..8c18d2992ea 100644
--- a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
@@ -81,7 +81,7 @@ describe('~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue', (
it('should select a different chart on change', async () => {
findSegmentedControl().vm.$emit('input', 1);
- const chart = wrapper.find(CiCdAnalyticsAreaChart);
+ const chart = wrapper.findComponent(CiCdAnalyticsAreaChart);
await nextTick();
@@ -92,7 +92,7 @@ describe('~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue', (
it('should not display charts if there are no charts', () => {
wrapper = createWrapper({ charts: [] });
- expect(wrapper.find(CiCdAnalyticsAreaChart).exists()).toBe(false);
+ expect(wrapper.findComponent(CiCdAnalyticsAreaChart).exists()).toBe(false);
});
describe('slots', () => {
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 3c91b913e67..8fb59f38ee1 100644
--- a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
@@ -44,7 +44,7 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
describe('overall statistics', () => {
it('displays the statistics list', () => {
- const list = wrapper.find(StatisticsList);
+ const list = wrapper.findComponent(StatisticsList);
expect(list.exists()).toBe(true);
expect(list.props('counts')).toEqual({
@@ -56,9 +56,9 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
});
it('displays the commit duration chart', () => {
- const chart = wrapper.find(GlColumnChart);
+ const chart = wrapper.findComponent(GlColumnChart);
- expect(chart.exists()).toBeTruthy();
+ expect(chart.exists()).toBe(true);
expect(chart.props('yAxisTitle')).toBe('Minutes');
expect(chart.props('xAxisTitle')).toBe('Commit');
expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData);
@@ -68,12 +68,12 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
describe('pipelines charts', () => {
it('displays the charts components', () => {
- expect(wrapper.find(CiCdAnalyticsCharts).exists()).toBe(true);
+ expect(wrapper.findComponent(CiCdAnalyticsCharts).exists()).toBe(true);
});
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
- const charts = wrapper.find(CiCdAnalyticsCharts);
+ const charts = wrapper.findComponent(CiCdAnalyticsCharts);
expect(charts.props()).toEqual({
charts: wrapper.vm.areaCharts,
chartOptions: wrapper.vm.$options.areaChartOptions,
diff --git a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
index 1db48ce05d7..1b06f7874a3 100644
--- a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
+++ b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
@@ -134,7 +134,7 @@ describe('Access Level Dropdown', () => {
await waitForPromises();
});
- it('renders headers for each section ', () => {
+ it('renders headers for each section', () => {
expect(findAllDropdownHeaders()).toHaveLength(4);
});
@@ -164,7 +164,7 @@ describe('Access Level Dropdown', () => {
expect(findDropdown().props('toggleClass')).toBe('gl-text-gray-500!');
});
- it('when no items selected, displays a default fallback label and has default CSS class ', () => {
+ it('when no items selected, displays a default fallback label and has default CSS class', () => {
expect(findDropdownToggleLabel()).toBe(i18n.selectUsers);
expect(findDropdown().props('toggleClass')).toBe('gl-text-gray-500!');
});
@@ -217,7 +217,7 @@ describe('Access Level Dropdown', () => {
});
describe('selecting an item', () => {
- it('selects the item on click and deselects on the next click ', async () => {
+ it('selects the item on click and deselects on the next click', async () => {
createComponent();
await waitForPromises();
@@ -230,7 +230,7 @@ describe('Access Level Dropdown', () => {
expect(item.props('isChecked')).toBe(false);
});
- it('emits a formatted update on selection ', async () => {
+ it('emits a formatted update on selection', async () => {
// ids: the items appear in that order in the dropdown
// 1 2 3 - roles
// 4 5 6 - groups
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 0a05832ceb6..329060b9d10 100644
--- a/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
+++ b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
@@ -27,9 +27,9 @@ describe('projects/settings/components/shared_runners', () => {
});
};
- const findErrorAlert = () => wrapper.find(GlAlert);
- const findSharedRunnersToggle = () => wrapper.find(GlToggle);
- const findToggleTooltip = () => wrapper.find(GlTooltip);
+ const findErrorAlert = () => wrapper.findComponent(GlAlert);
+ const findSharedRunnersToggle = () => wrapper.findComponent(GlToggle);
+ const findToggleTooltip = () => wrapper.findComponent(GlTooltip);
const getToggleValue = () => findSharedRunnersToggle().props('value');
const isToggleLoading = () => findSharedRunnersToggle().props('isLoading');
const isToggleDisabled = () => findSharedRunnersToggle().props('disabled');
diff --git a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
index e12c3aeedd6..e920cd48163 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
@@ -1,18 +1,55 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import BranchRules from '~/projects/settings/repository/branch_rules/app.vue';
+import BranchRules, { i18n } from '~/projects/settings/repository/branch_rules/app.vue';
+import BranchRule from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
+import branchRulesQuery from '~/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
+import createFlash from '~/flash';
+import { branchRulesMockResponse, propsDataMock } from './mock_data';
+
+jest.mock('~/flash');
+
+Vue.use(VueApollo);
describe('Branch rules app', () => {
let wrapper;
+ let fakeApollo;
+
+ const branchRulesQuerySuccessHandler = jest.fn().mockResolvedValue(branchRulesMockResponse);
+
+ const createComponent = async ({ queryHandler = branchRulesQuerySuccessHandler } = {}) => {
+ fakeApollo = createMockApollo([[branchRulesQuery, queryHandler]]);
+
+ wrapper = mountExtended(BranchRules, {
+ apolloProvider: fakeApollo,
+ propsData: {
+ ...propsDataMock,
+ },
+ });
- const createComponent = () => {
- wrapper = mountExtended(BranchRules);
+ await waitForPromises();
};
- const findTitle = () => wrapper.find('strong');
+ const findAllBranchRules = () => wrapper.findAllComponents(BranchRule);
+ const findEmptyState = () => wrapper.findByTestId('empty');
beforeEach(() => createComponent());
- it('renders a title', () => {
- expect(findTitle().text()).toBe('Branch');
+ it('displays an error if branch rules query fails', async () => {
+ await createComponent({ queryHandler: jest.fn().mockRejectedValue() });
+ expect(createFlash).toHaveBeenCalledWith({ message: i18n.queryError });
+ });
+
+ it('displays an empty state if no branch rules are present', async () => {
+ await createComponent({ queryHandler: jest.fn().mockRejectedValue() });
+ expect(findEmptyState().text()).toBe(i18n.emptyState);
+ });
+
+ it('renders branch rules', () => {
+ const { nodes } = branchRulesMockResponse.data.project.branchRules;
+ expect(findAllBranchRules().at(0).text()).toBe(nodes[0].name);
+ expect(findAllBranchRules().at(1).text()).toBe(nodes[1].name);
});
});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
new file mode 100644
index 00000000000..924dab60704
--- /dev/null
+++ b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
@@ -0,0 +1,58 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import BranchRule, {
+ i18n,
+} from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
+
+const defaultProps = {
+ name: 'main',
+ isDefault: true,
+ isProtected: true,
+ approvalDetails: ['requires approval from TEST', '2 status checks'],
+};
+
+describe('Branch rule', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(BranchRule, { propsData: { ...defaultProps, ...props } });
+ };
+
+ const findDefaultBadge = () => wrapper.findByText(i18n.defaultLabel);
+ const findProtectedBadge = () => wrapper.findByText(i18n.protectedLabel);
+ const findBranchName = () => wrapper.findByText(defaultProps.name);
+ const findProtectionDetailsList = () => wrapper.findByRole('list');
+ const findProtectionDetailsListItems = () => wrapper.findAllByRole('listitem');
+
+ beforeEach(() => createComponent());
+
+ it('renders the branch name', () => {
+ expect(findBranchName().exists()).toBe(true);
+ });
+
+ describe('badges', () => {
+ it('renders both default and protected badges', () => {
+ expect(findDefaultBadge().exists()).toBe(true);
+ expect(findProtectedBadge().exists()).toBe(true);
+ });
+
+ it('does not render default badge if isDefault is set to false', () => {
+ createComponent({ isDefault: false });
+ expect(findDefaultBadge().exists()).toBe(false);
+ });
+
+ it('does not render protected badge if isProtected is set to false', () => {
+ createComponent({ isProtected: false });
+ expect(findProtectedBadge().exists()).toBe(false);
+ });
+ });
+
+ it('does not render the protection details list of no details are present', () => {
+ createComponent({ approvalDetails: null });
+ expect(findProtectionDetailsList().exists()).toBe(false);
+ });
+
+ it('renders the protection details list items', () => {
+ expect(findProtectionDetailsListItems().at(0).text()).toBe(defaultProps.approvalDetails[0]);
+ expect(findProtectionDetailsListItems().at(1).text()).toBe(defaultProps.approvalDetails[1]);
+ });
+});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
new file mode 100644
index 00000000000..14ed35f047d
--- /dev/null
+++ b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
@@ -0,0 +1,25 @@
+export const branchRulesMockResponse = {
+ data: {
+ project: {
+ id: '123',
+ __typename: 'Project',
+ branchRules: {
+ __typename: 'BranchRuleConnection',
+ nodes: [
+ {
+ name: 'main',
+ __typename: 'BranchRule',
+ },
+ {
+ name: 'test-*',
+ __typename: 'BranchRule',
+ },
+ ],
+ },
+ },
+ },
+};
+
+export const propsDataMock = {
+ projectPath: 'some/project/path',
+};
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 62224612387..13f3eea277a 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,4 +1,4 @@
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
@@ -23,11 +23,16 @@ describe('ServiceDeskRoot', () => {
selectedTemplate: 'Bug',
selectedFileTemplateProjectId: 42,
templates: ['Bug', 'Documentation'],
+ publicProject: false,
};
- const getAlertText = () => wrapper.find(GlAlert).text();
+ const getAlertText = () => wrapper.findComponent(GlAlert).text();
- const createComponent = () => shallowMount(ServiceDeskRoot, { provide: provideData });
+ const createComponent = (customInject = {}) =>
+ shallowMount(ServiceDeskRoot, {
+ provide: { ...provideData, ...customInject },
+ stubs: { GlSprintf },
+ });
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
@@ -46,7 +51,7 @@ describe('ServiceDeskRoot', () => {
it('is rendered', () => {
wrapper = createComponent();
- expect(wrapper.find(ServiceDeskSetting).props()).toEqual({
+ expect(wrapper.findComponent(ServiceDeskSetting).props()).toEqual({
customEmail: provideData.customEmail,
customEmailEnabled: provideData.customEmailEnabled,
incomingEmail: provideData.initialIncomingEmail,
@@ -60,12 +65,31 @@ describe('ServiceDeskRoot', () => {
});
});
+ it('shows alert about email inference when current project is public', () => {
+ wrapper = createComponent({
+ publicProject: true,
+ });
+
+ const alertEl = wrapper.find('[data-testid="public-project-alert"]');
+ expect(alertEl.exists()).toBe(true);
+ expect(alertEl.text()).toContain(
+ 'This project is public. Non-members can guess the Service Desk email address, because it contains the group and project name.',
+ );
+
+ const alertBodyLink = alertEl.findComponent(GlLink);
+ expect(alertBodyLink.exists()).toBe(true);
+ expect(alertBodyLink.attributes('href')).toBe(
+ '/help/user/project/service_desk.html#using-a-custom-email-address',
+ );
+ expect(alertBodyLink.text()).toBe('How do I create a custom email address?');
+ });
+
describe('toggle event', () => {
describe('when toggling service desk on', () => {
beforeEach(async () => {
wrapper = createComponent();
- wrapper.find(ServiceDeskSetting).vm.$emit('toggle', true);
+ wrapper.findComponent(ServiceDeskSetting).vm.$emit('toggle', true);
await waitForPromises();
});
@@ -87,7 +111,7 @@ describe('ServiceDeskRoot', () => {
beforeEach(async () => {
wrapper = createComponent();
- wrapper.find(ServiceDeskSetting).vm.$emit('toggle', false);
+ wrapper.findComponent(ServiceDeskSetting).vm.$emit('toggle', false);
await waitForPromises();
});
@@ -119,7 +143,7 @@ describe('ServiceDeskRoot', () => {
projectKey: 'key',
};
- wrapper.find(ServiceDeskSetting).vm.$emit('save', payload);
+ wrapper.findComponent(ServiceDeskSetting).vm.$emit('save', payload);
await waitForPromises();
});
@@ -150,7 +174,7 @@ describe('ServiceDeskRoot', () => {
projectKey: 'key',
};
- wrapper.find(ServiceDeskSetting).vm.$emit('save', payload);
+ wrapper.findComponent(ServiceDeskSetting).vm.$emit('save', payload);
await waitForPromises();
});
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 aac1a418142..7c3f4e76ae5 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
@@ -8,13 +8,14 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('ServiceDeskSetting', () => {
let wrapper;
- const findButton = () => wrapper.find(GlButton);
- const findClipboardButton = () => wrapper.find(ClipboardButton);
+ const findButton = () => wrapper.findComponent(GlButton);
+ const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
const findIncomingEmail = () => wrapper.findByTestId('incoming-email');
const findIncomingEmailLabel = () => wrapper.findByTestId('incoming-email-label');
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findTemplateDropdown = () => wrapper.find(GlDropdown);
- const findToggle = () => wrapper.find(GlToggle);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findTemplateDropdown = () => wrapper.findComponent(GlDropdown);
+ const findToggle = () => wrapper.findComponent(GlToggle);
+ const findSuffixFormGroup = () => wrapper.findByTestId('suffix-form-group');
const createComponent = ({ props = {} } = {}) =>
extendedWrapper(
@@ -51,6 +52,32 @@ describe('ServiceDeskSetting', () => {
expect(findLoadingIcon().exists()).toBe(true);
expect(findIncomingEmail().exists()).toBe(false);
});
+
+ it('should display help text', () => {
+ expect(findSuffixFormGroup().text()).toContain(
+ 'To add a custom suffix, set up a Service Desk email address',
+ );
+ expect(findSuffixFormGroup().text()).not.toContain(
+ 'Add a suffix to Service Desk email address',
+ );
+ });
+ });
+ });
+
+ describe('when customEmailEnabled', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ props: { customEmailEnabled: true },
+ });
+ });
+
+ it('should not display help text', () => {
+ expect(findSuffixFormGroup().text()).not.toContain(
+ 'To add a custom suffix, set up a Service Desk email address',
+ );
+ expect(findSuffixFormGroup().text()).toContain(
+ 'Add a suffix to Service Desk email address',
+ );
});
});
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js
index cdb355f5a9b..6adcfbe8157 100644
--- a/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js
@@ -7,7 +7,7 @@ import { TEMPLATES } from './mock_data';
describe('ServiceDeskTemplateDropdown', () => {
let wrapper;
- const findTemplateDropdown = () => wrapper.find(GlDropdown);
+ const findTemplateDropdown = () => wrapper.findComponent(GlDropdown);
const createComponent = ({ props = {} } = {}) =>
extendedWrapper(
@@ -53,7 +53,7 @@ describe('ServiceDeskTemplateDropdown', () => {
props: { templates: TEMPLATES },
});
- const headerItems = wrapper.findAll(GlDropdownSectionHeader);
+ const headerItems = wrapper.findAllComponents(GlDropdownSectionHeader);
expect(headerItems).toHaveLength(1);
expect(headerItems.at(0).text()).toBe(TEMPLATES[0]);
@@ -68,7 +68,7 @@ describe('ServiceDeskTemplateDropdown', () => {
const expectedTemplates = templates[1];
- const items = wrapper.findAll(GlDropdownItem);
+ const items = wrapper.findAllComponents(GlDropdownItem);
const dropdownList = expectedTemplates.map((_, index) => items.at(index).text());
expect(items).toHaveLength(expectedTemplates.length);
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 882cb2c1199..6c5af5a2625 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -93,20 +93,20 @@ describe('Ref selector component', () => {
const findNoResults = () => wrapper.find('[data-testid="no-results"]');
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findBranchesSection = () => wrapper.find('[data-testid="branches-section"]');
- const findBranchDropdownItems = () => findBranchesSection().findAll(GlDropdownItem);
+ const findBranchDropdownItems = () => findBranchesSection().findAllComponents(GlDropdownItem);
const findFirstBranchDropdownItem = () => findBranchDropdownItems().at(0);
const findTagsSection = () => wrapper.find('[data-testid="tags-section"]');
- const findTagDropdownItems = () => findTagsSection().findAll(GlDropdownItem);
+ const findTagDropdownItems = () => findTagsSection().findAllComponents(GlDropdownItem);
const findFirstTagDropdownItem = () => findTagDropdownItems().at(0);
const findCommitsSection = () => wrapper.find('[data-testid="commits-section"]');
- const findCommitDropdownItems = () => findCommitsSection().findAll(GlDropdownItem);
+ const findCommitDropdownItems = () => findCommitsSection().findAllComponents(GlDropdownItem);
const findFirstCommitDropdownItem = () => findCommitDropdownItems().at(0);
//
@@ -530,13 +530,13 @@ describe('Ref selector component', () => {
});
it('renders a checkmark by the selected item', async () => {
- expect(findFirstBranchDropdownItem().find(GlIcon).element).toHaveClass(
+ expect(findFirstBranchDropdownItem().findComponent(GlIcon).element).toHaveClass(
'gl-visibility-hidden',
);
await selectFirstBranch();
- expect(findFirstBranchDropdownItem().find(GlIcon).element).not.toHaveClass(
+ expect(findFirstBranchDropdownItem().findComponent(GlIcon).element).not.toHaveClass(
'gl-visibility-hidden',
);
});
@@ -684,7 +684,8 @@ describe('Ref selector component', () => {
describe('validation state', () => {
const invalidClass = 'gl-inset-border-1-red-500!';
- const isInvalidClassApplied = () => wrapper.find(GlDropdown).props('toggleClass')[invalidClass];
+ const isInvalidClassApplied = () =>
+ wrapper.findComponent(GlDropdown).props('toggleClass')[invalidClass];
describe('valid state', () => {
describe('when the state prop is not provided', () => {
diff --git a/spec/frontend/related_issues/components/related_issuable_input_spec.js b/spec/frontend/related_issues/components/related_issuable_input_spec.js
index 7d11e3cffb0..f6a13856042 100644
--- a/spec/frontend/related_issues/components/related_issuable_input_spec.js
+++ b/spec/frontend/related_issues/components/related_issuable_input_spec.js
@@ -33,7 +33,7 @@ describe('RelatedIssuableInput', () => {
it('shows placeholder text', () => {
const wrapper = shallowMount(RelatedIssuableInput, { propsData });
- expect(wrapper.find({ ref: 'input' }).element.placeholder).toBe(
+ expect(wrapper.findComponent({ ref: 'input' }).element.placeholder).toBe(
'Paste issue link or <#issue id>',
);
});
@@ -54,7 +54,7 @@ describe('RelatedIssuableInput', () => {
},
});
- expect(wrapper.find({ ref: 'input' }).element.value).toBe('');
+ expect(wrapper.findComponent({ ref: 'input' }).element.value).toBe('');
});
it('does not have GfmAutoComplete', () => {
@@ -85,7 +85,7 @@ describe('RelatedIssuableInput', () => {
await nextTick();
- expect(document.activeElement).toBe(wrapper.find({ ref: 'input' }).element);
+ expect(document.activeElement).toBe(wrapper.findComponent({ ref: 'input' }).element);
});
});
@@ -100,7 +100,7 @@ describe('RelatedIssuableInput', () => {
const newInputValue = 'filling in things';
const untouchedRawReferences = newInputValue.trim().split(/\s/);
const touchedReference = untouchedRawReferences.pop();
- const input = wrapper.find({ ref: 'input' });
+ const input = wrapper.findComponent({ ref: 'input' });
input.element.value = newInputValue;
input.element.selectionStart = newInputValue.length;
diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js
index cb044b9e891..649d8eef6ec 100644
--- a/spec/frontend/releases/components/app_edit_new_spec.js
+++ b/spec/frontend/releases/components/app_edit_new_spec.js
@@ -220,7 +220,7 @@ describe('Release edit/new component', () => {
});
it('renders a checkbox to include release notes', () => {
- expect(wrapper.find(GlFormCheckbox).exists()).toBe(true);
+ expect(wrapper.findComponent(GlFormCheckbox).exists()).toBe(true);
});
});
@@ -238,7 +238,7 @@ describe('Release edit/new component', () => {
beforeEach(factory);
it('renders the asset links portion of the form', () => {
- expect(wrapper.find(AssetLinksForm).exists()).toBe(true);
+ expect(wrapper.findComponent(AssetLinksForm).exists()).toBe(true);
});
});
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index c2ea6900d6e..9ca25b3b69a 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -36,8 +36,8 @@ describe('Release show component', () => {
wrapper = null;
});
- const findLoadingSkeleton = () => wrapper.find(ReleaseSkeletonLoader);
- const findReleaseBlock = () => wrapper.find(ReleaseBlock);
+ const findLoadingSkeleton = () => wrapper.findComponent(ReleaseSkeletonLoader);
+ const findReleaseBlock = () => wrapper.findComponent(ReleaseBlock);
const expectLoadingIndicator = () => {
it('renders a loading indicator', () => {
diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js
index 17f079ba5a6..1ff5766b074 100644
--- a/spec/frontend/releases/components/asset_links_form_spec.js
+++ b/spec/frontend/releases/components/asset_links_form_spec.js
@@ -73,7 +73,7 @@ describe('Release edit component', () => {
it('calls the "addEmptyAssetLink" store method when the "Add another link" button is clicked', () => {
expect(actions.addEmptyAssetLink).not.toHaveBeenCalled();
- wrapper.find({ ref: 'addAnotherLinkButton' }).vm.$emit('click');
+ wrapper.findComponent({ ref: 'addAnotherLinkButton' }).vm.$emit('click');
expect(actions.addEmptyAssetLink).toHaveBeenCalledTimes(1);
});
@@ -92,7 +92,7 @@ describe('Release edit component', () => {
let newUrl;
beforeEach(() => {
- input = wrapper.find({ ref: 'urlInput' }).element;
+ input = wrapper.findComponent({ ref: 'urlInput' }).element;
linkIdToUpdate = release.assets.links[0].id;
newUrl = 'updated url';
});
@@ -118,7 +118,7 @@ describe('Release edit component', () => {
it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => {
expectStoreMethodNotToBeCalled();
- wrapper.find({ ref: 'urlInput' }).vm.$emit('change', newUrl);
+ wrapper.findComponent({ ref: 'urlInput' }).vm.$emit('change', newUrl);
expectStoreMethodToBeCalled();
});
@@ -150,7 +150,7 @@ describe('Release edit component', () => {
let newName;
beforeEach(() => {
- input = wrapper.find({ ref: 'nameInput' }).element;
+ input = wrapper.findComponent({ ref: 'nameInput' }).element;
linkIdToUpdate = release.assets.links[0].id;
newName = 'updated name';
});
@@ -176,7 +176,7 @@ describe('Release edit component', () => {
it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => {
expectStoreMethodNotToBeCalled();
- wrapper.find({ ref: 'nameInput' }).vm.$emit('change', newName);
+ wrapper.findComponent({ ref: 'nameInput' }).vm.$emit('change', newName);
expectStoreMethodToBeCalled();
});
@@ -208,7 +208,7 @@ describe('Release edit component', () => {
expect(actions.updateAssetLinkType).not.toHaveBeenCalled();
- wrapper.find({ ref: 'typeSelect' }).vm.$emit('change', newType);
+ wrapper.findComponent({ ref: 'typeSelect' }).vm.$emit('change', newType);
expect(actions.updateAssetLinkType).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkType).toHaveBeenCalledWith(expect.anything(), {
@@ -225,7 +225,7 @@ describe('Release edit component', () => {
});
it('selects the default asset type', () => {
- const selected = wrapper.find({ ref: 'typeSelect' }).element.value;
+ const selected = wrapper.findComponent({ ref: 'typeSelect' }).element.value;
expect(selected).toBe(DEFAULT_ASSET_LINK_TYPE);
});
diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js
index f0d02884305..2db1e9e38a2 100644
--- a/spec/frontend/releases/components/evidence_block_spec.js
+++ b/spec/frontend/releases/components/evidence_block_spec.js
@@ -32,19 +32,19 @@ describe('Evidence Block', () => {
});
it('renders the evidence icon', () => {
- expect(wrapper.find(GlIcon).props('name')).toBe('review-list');
+ expect(wrapper.findComponent(GlIcon).props('name')).toBe('review-list');
});
it('renders the title for the dowload link', () => {
- expect(wrapper.find(GlLink).text()).toBe(`v1.1-evidences-1.json`);
+ expect(wrapper.findComponent(GlLink).text()).toBe(`v1.1-evidences-1.json`);
});
it('renders the correct hover text for the download', () => {
- expect(wrapper.find(GlLink).attributes('title')).toBe('Download evidence JSON');
+ expect(wrapper.findComponent(GlLink).attributes('title')).toBe('Download evidence JSON');
});
it('renders the correct file link for download', () => {
- expect(wrapper.find(GlLink).attributes().download).toBe(`v1.1-evidences-1.json`);
+ expect(wrapper.findComponent(GlLink).attributes().download).toBe(`v1.1-evidences-1.json`);
});
describe('sha text', () => {
@@ -62,15 +62,15 @@ describe('Evidence Block', () => {
describe('copy to clipboard button', () => {
it('renders button', () => {
- expect(wrapper.find(ClipboardButton).exists()).toBe(true);
+ expect(wrapper.findComponent(ClipboardButton).exists()).toBe(true);
});
it('renders the correct hover text', () => {
- expect(wrapper.find(ClipboardButton).attributes('title')).toBe('Copy evidence SHA');
+ expect(wrapper.findComponent(ClipboardButton).attributes('title')).toBe('Copy evidence SHA');
});
it('copies the sha', () => {
- expect(wrapper.find(ClipboardButton).attributes('data-clipboard-text')).toBe(
+ expect(wrapper.findComponent(ClipboardButton).attributes('data-clipboard-text')).toBe(
release.evidences[0].sha,
);
});
diff --git a/spec/frontend/releases/components/issuable_stats_spec.js b/spec/frontend/releases/components/issuable_stats_spec.js
index 8fc0779da14..3ac75e138ee 100644
--- a/spec/frontend/releases/components/issuable_stats_spec.js
+++ b/spec/frontend/releases/components/issuable_stats_spec.js
@@ -16,9 +16,11 @@ describe('~/releases/components/issuable_stats.vue', () => {
});
};
- const findOpenStatLink = () => wrapper.find('[data-testid="open-stat"]').find(GlLink);
- const findMergedStatLink = () => wrapper.find('[data-testid="merged-stat"]').find(GlLink);
- const findClosedStatLink = () => wrapper.find('[data-testid="closed-stat"]').find(GlLink);
+ const findOpenStatLink = () => wrapper.find('[data-testid="open-stat"]').findComponent(GlLink);
+ const findMergedStatLink = () =>
+ wrapper.find('[data-testid="merged-stat"]').findComponent(GlLink);
+ const findClosedStatLink = () =>
+ wrapper.find('[data-testid="closed-stat"]').findComponent(GlLink);
beforeEach(() => {
defaultProps = {
diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js
index c63689e11ac..4f94e4dfd55 100644
--- a/spec/frontend/releases/components/release_block_assets_spec.js
+++ b/spec/frontend/releases/components/release_block_assets_spec.js
@@ -44,7 +44,7 @@ describe('Release block assets', () => {
});
it('renders the accordion as expanded by default', () => {
- const accordion = wrapper.find(GlCollapse);
+ const accordion = wrapper.findComponent(GlCollapse);
expect(accordion.exists()).toBe(true);
expect(accordion.isVisible()).toBe(true);
diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js
index 848e802df4b..8f4efad197f 100644
--- a/spec/frontend/releases/components/release_block_footer_spec.js
+++ b/spec/frontend/releases/components/release_block_footer_spec.js
@@ -38,16 +38,16 @@ describe('Release block footer', () => {
});
const commitInfoSection = () => wrapper.find('.js-commit-info');
- const commitInfoSectionLink = () => commitInfoSection().find(GlLink);
+ const commitInfoSectionLink = () => commitInfoSection().findComponent(GlLink);
const tagInfoSection = () => wrapper.find('.js-tag-info');
- const tagInfoSectionLink = () => tagInfoSection().find(GlLink);
+ const tagInfoSectionLink = () => tagInfoSection().findComponent(GlLink);
const authorDateInfoSection = () => wrapper.find('.js-author-date-info');
describe('with all props provided', () => {
beforeEach(() => factory());
it('renders the commit icon', () => {
- const commitIcon = commitInfoSection().find(GlIcon);
+ const commitIcon = commitInfoSection().findComponent(GlIcon);
expect(commitIcon.exists()).toBe(true);
expect(commitIcon.props('name')).toBe('commit');
@@ -62,14 +62,14 @@ describe('Release block footer', () => {
});
it('renders the tag icon', () => {
- const commitIcon = tagInfoSection().find(GlIcon);
+ const commitIcon = tagInfoSection().findComponent(GlIcon);
expect(commitIcon.exists()).toBe(true);
expect(commitIcon.props('name')).toBe('tag');
});
it('renders the tag name with a link', () => {
- const commitLink = tagInfoSection().find(GlLink);
+ const commitLink = tagInfoSection().findComponent(GlLink);
expect(commitLink.exists()).toBe(true);
expect(commitLink.text()).toBe(release.tagName);
@@ -120,7 +120,7 @@ describe('Release block footer', () => {
});
it("renders a link to the author's profile", () => {
- const authorLink = authorDateInfoSection().find(GlLink);
+ const authorLink = authorDateInfoSection().findComponent(GlLink);
expect(authorLink.exists()).toBe(true);
expect(authorLink.attributes('href')).toBe(release.author.webUrl);
diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js
index c9921185bad..fc421776d60 100644
--- a/spec/frontend/releases/components/release_block_header_spec.js
+++ b/spec/frontend/releases/components/release_block_header_spec.js
@@ -30,7 +30,7 @@ describe('Release block header', () => {
});
const findHeader = () => wrapper.find('h2');
- const findHeaderLink = () => findHeader().find(GlLink);
+ const findHeaderLink = () => findHeader().findComponent(GlLink);
const findEditButton = () => wrapper.find('.js-edit-button');
const findBadge = () => wrapper.findComponent(GlBadge);
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 84a0080965b..541d487091c 100644
--- a/spec/frontend/releases/components/release_block_milestone_info_spec.js
+++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js
@@ -43,7 +43,7 @@ describe('Release block milestone info', () => {
});
it('renders a progress bar that displays the correct percentage', () => {
- const progressBar = milestoneProgressBarContainer().find(GlProgressBar);
+ const progressBar = milestoneProgressBarContainer().findComponent(GlProgressBar);
expect(progressBar.exists()).toBe(true);
expect(progressBar.attributes()).toEqual(
@@ -58,7 +58,7 @@ describe('Release block milestone info', () => {
expect(milestoneListContainer().text()).toMatchInterpolatedText('Milestones 12.3 • 12.4');
milestones.forEach((m, i) => {
- const milestoneLink = milestoneListContainer().findAll(GlLink).at(i);
+ const milestoneLink = milestoneListContainer().findAllComponents(GlLink).at(i);
expect(milestoneLink.text()).toBe(m.title);
expect(milestoneLink.attributes('href')).toBe(m.webUrl);
@@ -72,7 +72,7 @@ describe('Release block milestone info', () => {
expect(issuesContainerText).toContain(`Issues ${totalIssueCount}`);
- const badge = issuesContainer().find(GlBadge);
+ const badge = issuesContainer().findComponent(GlBadge);
expect(badge.text()).toBe(totalIssueCount.toString());
expect(issuesContainerText).toContain('Open: 5 • Closed: 4');
@@ -107,7 +107,7 @@ describe('Release block milestone info', () => {
});
const clickShowMoreFewerButton = async () => {
- milestoneListContainer().find(GlButton).trigger('click');
+ milestoneListContainer().findComponent(GlButton).trigger('click');
await nextTick();
};
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index 17e2af687a6..096c3db8902 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -74,7 +74,7 @@ describe('Release block', () => {
});
it('renders the footer', () => {
- expect(wrapper.find(ReleaseBlockFooter).exists()).toBe(true);
+ expect(wrapper.findComponent(ReleaseBlockFooter).exists()).toBe(true);
});
});
@@ -133,7 +133,7 @@ describe('Release block', () => {
describe('evidence block', () => {
it('renders the evidence block when the evidence is available', () => {
return factory(release).then(() => {
- expect(wrapper.find(EvidenceBlock).exists()).toBe(true);
+ expect(wrapper.findComponent(EvidenceBlock).exists()).toBe(true);
});
});
@@ -141,7 +141,7 @@ describe('Release block', () => {
release.evidences = [];
return factory(release).then(() => {
- expect(wrapper.find(EvidenceBlock).exists()).toBe(false);
+ expect(wrapper.findComponent(EvidenceBlock).exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/releases/components/release_skeleton_loader_spec.js b/spec/frontend/releases/components/release_skeleton_loader_spec.js
index 7f81081ff6c..76dfe0d9777 100644
--- a/spec/frontend/releases/components/release_skeleton_loader_spec.js
+++ b/spec/frontend/releases/components/release_skeleton_loader_spec.js
@@ -10,6 +10,6 @@ describe('release_skeleton_loader.vue', () => {
});
it('renders a GlSkeletonLoader', () => {
- expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
+ expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
});
});
diff --git a/spec/frontend/releases/components/tag_field_exsting_spec.js b/spec/frontend/releases/components/tag_field_exsting_spec.js
index f45a28392b7..8105aa4f6f2 100644
--- a/spec/frontend/releases/components/tag_field_exsting_spec.js
+++ b/spec/frontend/releases/components/tag_field_exsting_spec.js
@@ -20,7 +20,7 @@ describe('releases/components/tag_field_existing', () => {
});
};
- const findInput = () => wrapper.find(GlFormInput);
+ const findInput = () => wrapper.findComponent(GlFormInput);
const findHelp = () => wrapper.find('[data-testid="tag-name-help"]');
beforeEach(() => {
diff --git a/spec/frontend/releases/components/tag_field_new_spec.js b/spec/frontend/releases/components/tag_field_new_spec.js
index 9f500c318ea..b8047cae8c2 100644
--- a/spec/frontend/releases/components/tag_field_new_spec.js
+++ b/spec/frontend/releases/components/tag_field_new_spec.js
@@ -79,12 +79,12 @@ describe('releases/components/tag_field_new', () => {
});
const findTagNameFormGroup = () => wrapper.find('[data-testid="tag-name-field"]');
- const findTagNameDropdown = () => findTagNameFormGroup().find(RefSelectorStub);
+ const findTagNameDropdown = () => findTagNameFormGroup().findComponent(RefSelectorStub);
const findCreateFromFormGroup = () => wrapper.find('[data-testid="create-from-field"]');
- const findCreateFromDropdown = () => findCreateFromFormGroup().find(RefSelectorStub);
+ const findCreateFromDropdown = () => findCreateFromFormGroup().findComponent(RefSelectorStub);
- const findCreateNewTagOption = () => wrapper.find(GlDropdownItem);
+ const findCreateNewTagOption = () => wrapper.findComponent(GlDropdownItem);
describe('"Tag name" field', () => {
describe('rendering and behavior', () => {
diff --git a/spec/frontend/releases/components/tag_field_spec.js b/spec/frontend/releases/components/tag_field_spec.js
index e7b9aa4abbb..85a40f02c53 100644
--- a/spec/frontend/releases/components/tag_field_spec.js
+++ b/spec/frontend/releases/components/tag_field_spec.js
@@ -21,8 +21,8 @@ describe('releases/components/tag_field', () => {
wrapper = shallowMount(TagField, { store });
};
- const findTagFieldNew = () => wrapper.find(TagFieldNew);
- const findTagFieldExisting = () => wrapper.find(TagFieldExisting);
+ const findTagFieldNew = () => wrapper.findComponent(TagFieldNew);
+ const findTagFieldExisting = () => wrapper.findComponent(TagFieldExisting);
afterEach(() => {
wrapper.destroy();
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js
index ce3b690213c..48fba3adb24 100644
--- a/spec/frontend/releases/stores/modules/detail/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js
@@ -352,6 +352,32 @@ describe('Release edit/new actions', () => {
});
});
+ describe('when the GraphQL returns errors as data', () => {
+ beforeEach(() => {
+ gqClient.mutate.mockResolvedValue({ data: { releaseCreate: { errors: ['Yikes!'] } } });
+ });
+
+ it(`commits ${types.RECEIVE_SAVE_RELEASE_ERROR} with an error object`, () => {
+ return testAction(actions.createRelease, undefined, state, [
+ {
+ type: types.RECEIVE_SAVE_RELEASE_ERROR,
+ payload: expect.any(Error),
+ },
+ ]);
+ });
+
+ it(`shows a flash message`, () => {
+ return actions
+ .createRelease({ commit: jest.fn(), dispatch: jest.fn(), state, getters: {} })
+ .then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'Yikes!',
+ });
+ });
+ });
+ });
+
describe('when the GraphQL network request fails', () => {
beforeEach(() => {
gqClient.mutate.mockRejectedValue(error);
diff --git a/spec/frontend/releases/stores/modules/detail/getters_spec.js b/spec/frontend/releases/stores/modules/detail/getters_spec.js
index 4ac6eaebaa2..2982dc5c46c 100644
--- a/spec/frontend/releases/stores/modules/detail/getters_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/getters_spec.js
@@ -320,7 +320,9 @@ describe('Release edit/new getters', () => {
it(description, () => {
const expectedVariablesObject = { input: expect.objectContaining(expectedVariables) };
- const actualVariables = getters.releaseUpdateMutatationVariables(state);
+ const actualVariables = getters.releaseUpdateMutatationVariables(state, {
+ releasedAtChanged: Object.hasOwn(state.release, 'releasedAt'),
+ });
expect(actualVariables).toEqual(expectedVariablesObject);
});
@@ -409,4 +411,19 @@ describe('Release edit/new getters', () => {
},
);
});
+
+ describe('releasedAtChange', () => {
+ it('is false if the released at date has not changed', () => {
+ const date = new Date();
+ expect(
+ getters.releasedAtChanged({ originalReleasedAt: date, release: { releasedAt: date } }),
+ ).toBe(false);
+ });
+
+ it('is true if the date changed', () => {
+ const originalReleasedAt = new Date();
+ const releasedAt = new Date(2022, 5, 30);
+ expect(getters.releasedAtChanged({ originalReleasedAt, release: { releasedAt } })).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/releases/stores/modules/detail/mutations_spec.js b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
index 60b57c7a7ff..8bbf550b77d 100644
--- a/spec/frontend/releases/stores/modules/detail/mutations_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
@@ -36,6 +36,12 @@ describe('Release edit/new mutations', () => {
},
});
});
+
+ it('saves the original released at date as well', () => {
+ mutations[types.INITIALIZE_EMPTY_RELEASE](state);
+
+ expect(state.originalReleasedAt).toEqual(new Date());
+ });
});
describe(`${types.REQUEST_RELEASE}`, () => {
@@ -57,6 +63,7 @@ describe('Release edit/new mutations', () => {
expect(state.release).toEqual(release);
expect(state.originalRelease).toEqual(release);
+ expect(state.originalReleasedAt).toEqual(release.releasedAt);
});
});
diff --git a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js
index ddabb7194cb..d835ca4c733 100644
--- a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js
+++ b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js
@@ -41,7 +41,7 @@ describe('CustomMetricsForm', () => {
});
it('Displays the issue message', () => {
- const description = wrapper.find({ ref: 'accessibility-issue-description' }).text();
+ const description = wrapper.findComponent({ ref: 'accessibility-issue-description' }).text();
expect(description).toContain(`Message: ${issue.message}`);
});
@@ -49,7 +49,7 @@ describe('CustomMetricsForm', () => {
describe('When an issue code is present', () => {
it('Creates the correct URL for learning more about the issue code', () => {
const learnMoreUrl = wrapper
- .find({ ref: 'accessibility-issue-learn-more' })
+ .findComponent({ ref: 'accessibility-issue-learn-more' })
.attributes('href');
expect(learnMoreUrl).toBe(issue.learnMoreUrl);
@@ -66,7 +66,7 @@ describe('CustomMetricsForm', () => {
it('Creates a URL leading to the overview documentation page', () => {
const learnMoreUrl = wrapper
- .find({ ref: 'accessibility-issue-learn-more' })
+ .findComponent({ ref: 'accessibility-issue-learn-more' })
.attributes('href');
expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html');
@@ -83,7 +83,7 @@ describe('CustomMetricsForm', () => {
it('Creates a URL leading to the overview documentation page', () => {
const learnMoreUrl = wrapper
- .find({ ref: 'accessibility-issue-learn-more' })
+ .findComponent({ ref: 'accessibility-issue-learn-more' })
.attributes('href');
expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html');
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 34b1cdd92bc..9d3535291eb 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
@@ -114,7 +114,7 @@ describe('Grouped accessibility reports app', () => {
});
it('renders custom accessibility issue body', () => {
- const issueBody = wrapper.find(AccessibilityIssueBody);
+ const issueBody = wrapper.findComponent(AccessibilityIssueBody);
expect(issueBody.props('issue').code).toBe(mockReport.new_errors[0].code);
expect(issueBody.props('issue').message).toBe(mockReport.new_errors[0].message);
diff --git a/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js b/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
index 17f07ac2b8f..c32b52d9e77 100644
--- a/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
+++ b/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
@@ -8,7 +8,7 @@ describe('code quality issue body issue body', () => {
let wrapper;
const findSeverityIcon = () => wrapper.findByTestId('codequality-severity-icon');
- const findGlIcon = () => wrapper.find(GlIcon);
+ const findGlIcon = () => wrapper.findComponent(GlIcon);
const codequalityIssue = {
name:
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 b61b65c2713..962ff068b92 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
@@ -30,7 +30,7 @@ describe('Grouped code quality reports app', () => {
};
const findWidget = () => wrapper.find('.js-codequality-widget');
- const findIssueBody = () => wrapper.find(CodequalityIssueBody);
+ const findIssueBody = () => wrapper.findComponent(CodequalityIssueBody);
beforeEach(() => {
const { state, ...storeConfig } = getStoreConfig();
diff --git a/spec/frontend/reports/components/grouped_issues_list_spec.js b/spec/frontend/reports/components/grouped_issues_list_spec.js
index 95ef0bcbcc7..6c0275dc47d 100644
--- a/spec/frontend/reports/components/grouped_issues_list_spec.js
+++ b/spec/frontend/reports/components/grouped_issues_list_spec.js
@@ -30,7 +30,7 @@ describe('Grouped Issues List', () => {
},
});
- expect(wrapper.find(SmartVirtualList).props()).toMatchSnapshot();
+ expect(wrapper.findComponent(SmartVirtualList).props()).toMatchSnapshot();
});
describe('without data', () => {
@@ -43,7 +43,7 @@ describe('Grouped Issues List', () => {
});
it.each(['resolved', 'unresolved'])('does not render report items for %s issues', () => {
- expect(wrapper.find(ReportItem).exists()).toBe(false);
+ expect(wrapper.findComponent(ReportItem).exists()).toBe(false);
});
});
@@ -67,7 +67,7 @@ describe('Grouped Issues List', () => {
propsData: { [`${issueName}Issues`]: issues },
});
- expect(wrapper.findAll(ReportItem)).toHaveLength(issues.length);
+ expect(wrapper.findAllComponents(ReportItem)).toHaveLength(issues.length);
});
it('renders a report item with the correct props', () => {
@@ -81,7 +81,7 @@ describe('Grouped Issues List', () => {
},
});
- expect(wrapper.find(ReportItem).props()).toMatchSnapshot();
+ expect(wrapper.findComponent(ReportItem).props()).toMatchSnapshot();
});
});
});
diff --git a/spec/frontend/reports/components/report_item_spec.js b/spec/frontend/reports/components/report_item_spec.js
index a7243c5377b..b52c163eb26 100644
--- a/spec/frontend/reports/components/report_item_spec.js
+++ b/spec/frontend/reports/components/report_item_spec.js
@@ -16,7 +16,7 @@ describe('ReportItem', () => {
},
});
- expect(wrapper.find(IssueStatusIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(IssueStatusIcon).exists()).toBe(false);
});
it('shows status icon when unspecified', () => {
@@ -28,7 +28,7 @@ describe('ReportItem', () => {
},
});
- expect(wrapper.find(IssueStatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(IssueStatusIcon).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/reports/grouped_test_report/components/modal_spec.js b/spec/frontend/reports/grouped_test_report/components/modal_spec.js
index 3de81f754fd..e8564d2428d 100644
--- a/spec/frontend/reports/grouped_test_report/components/modal_spec.js
+++ b/spec/frontend/reports/grouped_test_report/components/modal_spec.js
@@ -40,7 +40,9 @@ describe('Grouped Test Reports Modal', () => {
});
it('renders code block', () => {
- expect(wrapper.find(CodeBlock).props().code).toEqual(modalDataStructure.system_output.value);
+ expect(wrapper.findComponent(CodeBlock).props().code).toEqual(
+ modalDataStructure.system_output.value,
+ );
});
it('renders link', () => {
diff --git a/spec/frontend/reports/grouped_test_report/store/actions_spec.js b/spec/frontend/reports/grouped_test_report/store/actions_spec.js
index 5876827c548..7469c31cf84 100644
--- a/spec/frontend/reports/grouped_test_report/store/actions_spec.js
+++ b/spec/frontend/reports/grouped_test_report/store/actions_spec.js
@@ -61,7 +61,7 @@ describe('Reports Store Actions', () => {
});
describe('success', () => {
- it('dispatches requestReports and receiveReportsSuccess ', () => {
+ it('dispatches requestReports and receiveReportsSuccess', () => {
mock
.onGet(`${TEST_HOST}/endpoint.json`)
.replyOnce(200, { summary: {}, suites: [{ name: 'rspec' }] });
@@ -89,7 +89,7 @@ describe('Reports Store Actions', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
- it('dispatches requestReports and receiveReportsError ', () => {
+ it('dispatches requestReports and receiveReportsError', () => {
return testAction(
fetchReports,
null,
diff --git a/spec/frontend/reports/mock_data/new_failures_with_null_files_report.json b/spec/frontend/reports/mock_data/new_failures_with_null_files_report.json
new file mode 100644
index 00000000000..28ee7d194b9
--- /dev/null
+++ b/spec/frontend/reports/mock_data/new_failures_with_null_files_report.json
@@ -0,0 +1,40 @@
+{
+ "summary": { "total": 11, "resolved": 0, "errored": 0, "failed": 2 },
+ "suites": [
+ {
+ "name": "rspec:pg",
+ "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 2 },
+ "new_failures": [
+ {
+ "result": "failure",
+ "name": "Test#sum when a is 1 and b is 2 returns summary",
+ "file": null,
+ "execution_time": 0.009411,
+ "system_output": "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'"
+ },
+ {
+ "result": "failure",
+ "name": "Test#sum when a is 100 and b is 200 returns summary",
+ "file": null,
+ "execution_time": 0.000162,
+ "system_output": "Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'"
+ }
+ ],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ },
+ {
+ "name": "java ant",
+ "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ }
+ ]
+}
diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
index cb56f392ec9..01494cb6a24 100644
--- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
+++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
@@ -20,7 +20,8 @@ exports[`Repository last commit component renders commit widget 1`] = `
class="commit-detail flex-list gl-display-flex gl-justify-content-space-between gl-align-items-flex-start gl-flex-grow-1 gl-min-w-0"
>
<div
- class="commit-content qa-commit-content"
+ class="commit-content"
+ data-qa-selector="commit_content"
>
<gl-link-stub
class="commit-row-message item-title"
diff --git a/spec/frontend/repository/components/blob_button_group_spec.js b/spec/frontend/repository/components/blob_button_group_spec.js
index d5b882bd715..33a85c04fcf 100644
--- a/spec/frontend/repository/components/blob_button_group_spec.js
+++ b/spec/frontend/repository/components/blob_button_group_spec.js
@@ -66,12 +66,12 @@ describe('BlobButtonGroup component', () => {
});
it('renders both the replace and delete button', () => {
- expect(wrapper.findAll(GlButton)).toHaveLength(2);
+ expect(wrapper.findAllComponents(GlButton)).toHaveLength(2);
});
it('renders the buttons in the correct order', () => {
- expect(wrapper.findAll(GlButton).at(0).text()).toBe('Replace');
- expect(wrapper.findAll(GlButton).at(1).text()).toBe('Delete');
+ expect(wrapper.findAllComponents(GlButton).at(0).text()).toBe('Replace');
+ expect(wrapper.findAllComponents(GlButton).at(1).text()).toBe('Delete');
});
it('triggers the UploadBlobModal from the replace button', () => {
@@ -97,14 +97,14 @@ describe('BlobButtonGroup component', () => {
findReplaceButton().trigger('click');
expect(findUploadBlobModal().vm.show).not.toHaveBeenCalled();
- expect(wrapper.emitted().fork).toBeTruthy();
+ expect(wrapper.emitted().fork).toHaveLength(1);
});
it('does not trigger the DeleteBlobModal from the delete button', () => {
findDeleteButton().trigger('click');
expect(findDeleteBlobModal().vm.show).not.toHaveBeenCalled();
- expect(wrapper.emitted().fork).toBeTruthy();
+ expect(wrapper.emitted().fork).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index 0f7cf4e61b2..6ece72c41bb 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -17,7 +17,8 @@ import { loadViewer } from '~/repository/components/blob_viewers';
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
-import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
+import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql';
+import projectInfoQuery from '~/repository/queries/project_info.query.graphql';
import userInfoQuery from '~/repository/queries/user_info.query.graphql';
import applicationInfoQuery from '~/repository/queries/application_info.query.graphql';
import CodeIntelligence from '~/code_navigation/components/app.vue';
@@ -45,8 +46,9 @@ jest.mock('~/lib/utils/common_utils');
jest.mock('~/blob/line_highlighter');
let wrapper;
-let mockResolver;
+let blobInfoMockResolver;
let userInfoMockResolver;
+let projectInfoMockResolver;
let applicationInfoMockResolver;
const mockAxios = new MockAdapter(axios);
@@ -74,22 +76,40 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute
highlightJs = true,
} = mockData;
- const project = {
+ const blobInfo = {
...projectMock,
+ repository: {
+ empty,
+ blobs: { nodes: [blob] },
+ },
+ };
+
+ const projectInfo = {
+ __typename: 'Project',
+ id: '123',
userPermissions: {
pushCode,
forkProject,
downloadCode,
createMergeRequestIn,
},
- repository: {
- empty,
- blobs: { nodes: [blob] },
+ pathLocks: {
+ nodes: [
+ {
+ id: 'test',
+ path: 'locked_file.js',
+ user: { id: '123', username: 'root' },
+ },
+ ],
},
};
- mockResolver = jest.fn().mockResolvedValue({
- data: { isBinary, project },
+ projectInfoMockResolver = jest.fn().mockResolvedValue({
+ data: { project: projectInfo },
+ });
+
+ blobInfoMockResolver = jest.fn().mockResolvedValue({
+ data: { isBinary, project: blobInfo },
});
userInfoMockResolver = jest.fn().mockResolvedValue({
@@ -101,8 +121,9 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute
});
const fakeApollo = createMockApollo([
- [blobInfoQuery, mockResolver],
+ [blobInfoQuery, blobInfoMockResolver],
[userInfoQuery, userInfoMockResolver],
+ [projectInfoQuery, projectInfoMockResolver],
[applicationInfoQuery, applicationInfoMockResolver],
]);
@@ -129,7 +150,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ project, isBinary });
+ wrapper.setData({ project: blobInfo, isBinary });
await waitForPromises();
};
@@ -504,14 +525,16 @@ describe('Blob content viewer component', () => {
async ({ highlightJs, shouldFetchRawText }) => {
await createComponent({ highlightJs });
- expect(mockResolver).toHaveBeenCalledWith(expect.objectContaining({ shouldFetchRawText }));
+ expect(blobInfoMockResolver).toHaveBeenCalledWith(
+ expect.objectContaining({ shouldFetchRawText }),
+ );
},
);
it('is called with originalBranch value if the prop has a value', async () => {
await createComponent({ inject: { originalBranch: 'some-branch' } });
- expect(mockResolver).toHaveBeenCalledWith(
+ expect(blobInfoMockResolver).toHaveBeenCalledWith(
expect.objectContaining({
ref: 'some-branch',
}),
@@ -521,7 +544,7 @@ describe('Blob content viewer component', () => {
it('is called with ref value if the originalBranch prop has no value', async () => {
await createComponent();
- expect(mockResolver).toHaveBeenCalledWith(
+ expect(blobInfoMockResolver).toHaveBeenCalledWith(
expect.objectContaining({
ref: 'default-ref',
}),
diff --git a/spec/frontend/repository/components/blob_viewers/csv_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/csv_viewer_spec.js
index 7d43e4e660b..c6b9737dde2 100644
--- a/spec/frontend/repository/components/blob_viewers/csv_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/csv_viewer_spec.js
@@ -21,7 +21,7 @@ describe('CSV Viewer', () => {
it('renders a Source Editor component', () => {
createComponent();
expect(findCsvViewerComp().exists()).toBe(true);
- expect(findCsvViewerComp().props('remoteFile')).toBeTruthy();
+ expect(findCsvViewerComp().props('remoteFile')).toBe(true);
expect(findCsvViewerComp().props('csv')).toBe(DEFAULT_BLOB_DATA.rawPath);
});
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index 40b32904589..c2f34f79f89 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -39,8 +39,8 @@ describe('Repository breadcrumbs component', () => {
});
};
- const findUploadBlobModal = () => wrapper.find(UploadBlobModal);
- const findNewDirectoryModal = () => wrapper.find(NewDirectoryModal);
+ const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
+ const findNewDirectoryModal = () => wrapper.findComponent(NewDirectoryModal);
afterEach(() => {
wrapper.destroy();
@@ -55,7 +55,7 @@ describe('Repository breadcrumbs component', () => {
`('renders $linkCount links for path $path', ({ path, linkCount }) => {
factory(path);
- expect(wrapper.findAll(RouterLinkStub).length).toEqual(linkCount);
+ expect(wrapper.findAllComponents(RouterLinkStub).length).toEqual(linkCount);
});
it.each`
@@ -68,14 +68,14 @@ describe('Repository breadcrumbs component', () => {
'links to the correct router path when routeName is $routeName',
({ routeName, path, linkTo }) => {
factory(path, {}, { name: routeName });
- expect(wrapper.findAll(RouterLinkStub).at(3).props('to')).toEqual(linkTo);
+ expect(wrapper.findAllComponents(RouterLinkStub).at(3).props('to')).toEqual(linkTo);
},
);
it('escapes hash in directory path', () => {
factory('app/assets/javascripts#');
- expect(wrapper.findAll(RouterLinkStub).at(3).props('to')).toEqual(
+ expect(wrapper.findAllComponents(RouterLinkStub).at(3).props('to')).toEqual(
'/-/tree/app/assets/javascripts%23',
);
});
@@ -83,7 +83,9 @@ describe('Repository breadcrumbs component', () => {
it('renders last link as active', () => {
factory('app/assets');
- expect(wrapper.findAll(RouterLinkStub).at(2).attributes('aria-current')).toEqual('page');
+ expect(wrapper.findAllComponents(RouterLinkStub).at(2).attributes('aria-current')).toEqual(
+ 'page',
+ );
});
it('does not render add to tree dropdown when permissions are false', async () => {
@@ -95,7 +97,7 @@ describe('Repository breadcrumbs component', () => {
await nextTick();
- expect(wrapper.find(GlDropdown).exists()).toBe(false);
+ expect(wrapper.findComponent(GlDropdown).exists()).toBe(false);
});
it.each`
@@ -109,7 +111,7 @@ describe('Repository breadcrumbs component', () => {
'does render add to tree dropdown $isRendered when route is $routeName',
({ routeName, isRendered }) => {
factory('app/assets/javascripts.js', { canCollaborate: true }, { name: routeName });
- expect(wrapper.find(GlDropdown).exists()).toBe(isRendered);
+ expect(wrapper.findComponent(GlDropdown).exists()).toBe(isRendered);
},
);
@@ -122,7 +124,7 @@ describe('Repository breadcrumbs component', () => {
await nextTick();
- expect(wrapper.find(GlDropdown).exists()).toBe(true);
+ expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
});
describe('renders the upload blob modal', () => {
diff --git a/spec/frontend/repository/components/delete_blob_modal_spec.js b/spec/frontend/repository/components/delete_blob_modal_spec.js
index 785783b2e75..b5996816ad8 100644
--- a/spec/frontend/repository/components/delete_blob_modal_spec.js
+++ b/spec/frontend/repository/components/delete_blob_modal_spec.js
@@ -84,7 +84,7 @@ describe('DeleteBlobModal', () => {
${GlToggle} | ${'true'} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
${GlToggle} | ${undefined} | ${true} | ${'same-branch'} | ${'same-branch'} | ${false}
`(
- 'has the correct form fields ',
+ 'has the correct form fields',
({ component, defaultValue, canPushCode, targetBranch, originalBranch, exist }) => {
createComponent({
canPushCode,
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index 3783b34e33a..bf9528953b6 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -190,11 +190,16 @@ describe('Repository last commit component', () => {
});
it('expands commit description when clicking expander', async () => {
+ expect(findCommitRowDescription().classes('d-block')).toBe(false);
+ expect(findTextExpander().classes('open')).toBe(false);
+ expect(findTextExpander().props('selected')).toBe(false);
+
findTextExpander().vm.$emit('click');
await nextTick();
- expect(findCommitRowDescription().isVisible()).toBe(true);
- expect(findTextExpander().classes()).toContain('open');
+ expect(findCommitRowDescription().classes('d-block')).toBe(true);
+ expect(findTextExpander().classes('open')).toBe(true);
+ expect(findTextExpander().props('selected')).toBe(true);
});
});
diff --git a/spec/frontend/repository/components/new_directory_modal_spec.js b/spec/frontend/repository/components/new_directory_modal_spec.js
index e1c50d63851..aaf751a9a8d 100644
--- a/spec/frontend/repository/components/new_directory_modal_spec.js
+++ b/spec/frontend/repository/components/new_directory_modal_spec.js
@@ -107,7 +107,7 @@ describe('NewDirectoryModal', () => {
${findMrToggle} | ${'true'} | ${true} | ${'new-target-branch'} | ${'master'} | ${true}
${findMrToggle} | ${'true'} | ${true} | ${'master'} | ${'master'} | ${true}
`(
- 'has the correct form fields ',
+ 'has the correct form fields',
({ component, defaultValue, canPushCode, targetBranch, originalBranch, exist }) => {
createComponent({
canPushCode,
diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js
index 0d9bfc62ed5..e4eba65795e 100644
--- a/spec/frontend/repository/components/preview/index_spec.js
+++ b/spec/frontend/repository/components/preview/index_spec.js
@@ -68,6 +68,6 @@ describe('Repository file preview component', () => {
vm.setData({ loading: 1 });
await nextTick();
- expect(vm.find(GlLoadingIcon).exists()).toBe(true);
+ expect(vm.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index ff0371b5c07..697d2dcc7f5 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -38,6 +38,7 @@ const MOCK_BLOBS = [
const MOCK_COMMITS = [
{
fileName: 'blob.md',
+ filePath: 'test_dir/blob.md',
type: 'blob',
commit: {
message: 'Updated blob.md',
@@ -45,6 +46,7 @@ const MOCK_COMMITS = [
},
{
fileName: 'blob2.md',
+ filePath: 'test_dir/blob2.md',
type: 'blob',
commit: {
message: 'Updated blob2.md',
@@ -52,11 +54,20 @@ const MOCK_COMMITS = [
},
{
fileName: 'blob3.md',
+ filePath: 'test_dir/blob3.md',
type: 'blob',
commit: {
message: 'Updated blob3.md',
},
},
+ {
+ fileName: 'root_blob.md',
+ filePath: '/root_blob.md',
+ type: 'blob',
+ commit: {
+ message: 'Updated root_blob.md',
+ },
+ },
];
function factory({ path, isLoading = false, hasMore = true, entries = {}, commits = [] }) {
@@ -77,6 +88,8 @@ function factory({ path, isLoading = false, hasMore = true, entries = {}, commit
});
}
+const findTableRows = () => vm.findAllComponents(TableRow);
+
describe('Repository table component', () => {
afterEach(() => {
vm.destroy();
@@ -108,14 +121,14 @@ describe('Repository table component', () => {
it('renders table rows', () => {
factory({
- path: '/',
+ path: 'test_dir',
entries: {
blobs: MOCK_BLOBS,
},
commits: MOCK_COMMITS,
});
- const rows = vm.findAll(TableRow);
+ const rows = findTableRows();
expect(rows.length).toEqual(3);
expect(rows.at(2).attributes().mode).toEqual('120000');
@@ -123,6 +136,28 @@ describe('Repository table component', () => {
expect(rows.at(2).props().commitInfo).toEqual(MOCK_COMMITS[2]);
});
+ it('renders correct commit info for blobs in the root', () => {
+ factory({
+ path: '/',
+ entries: {
+ blobs: [
+ {
+ id: '126abc',
+ sha: '126abc',
+ flatPath: 'root_blob.md',
+ name: 'root_blob.md',
+ type: 'blob',
+ webUrl: 'http://test.com',
+ mode: '120000',
+ },
+ ],
+ },
+ commits: MOCK_COMMITS,
+ });
+
+ expect(findTableRows().at(0).props().commitInfo).toEqual(MOCK_COMMITS[3]);
+ });
+
describe('Show more button', () => {
const showMoreButton = () => vm.find(GlButton);
diff --git a/spec/frontend/repository/components/upload_blob_modal_spec.js b/spec/frontend/repository/components/upload_blob_modal_spec.js
index bf024baa627..505ff7f3dd6 100644
--- a/spec/frontend/repository/components/upload_blob_modal_spec.js
+++ b/spec/frontend/repository/components/upload_blob_modal_spec.js
@@ -217,7 +217,7 @@ describe('UploadBlobModal', () => {
createComponent();
});
- it('displays the default "Upload new file" modal title ', () => {
+ it('displays the default "Upload new file" modal title', () => {
expect(findModal().props('title')).toBe('Upload new file');
});
diff --git a/spec/frontend/repository/log_tree_spec.js b/spec/frontend/repository/log_tree_spec.js
index e3b4dcb8acc..c1309539b6d 100644
--- a/spec/frontend/repository/log_tree_spec.js
+++ b/spec/frontend/repository/log_tree_spec.js
@@ -30,7 +30,7 @@ describe('resolveCommit', () => {
{ fileName: 'index.js', filePath: '/app/assets/index.js' },
];
- resolveCommit(commits, '', resolver);
+ resolveCommit(commits, '/', resolver);
expect(resolver.resolve).toHaveBeenCalledWith({
fileName: 'index.js',
@@ -107,14 +107,14 @@ describe('fetchLogsTree', () => {
}));
it('calls entry resolver', () =>
- fetchLogsTree(client, '', '0', resolver).then(() => {
+ fetchLogsTree(client, 'test', '0', resolver).then(() => {
expect(resolver.resolve).toHaveBeenCalledWith(
expect.objectContaining({
__typename: 'LogTreeCommit',
commitPath: 'https://test.com',
committedDate: '2019-01-01',
fileName: 'index.js',
- filePath: '/index.js',
+ filePath: 'test/index.js',
message: 'testing message',
sha: '123',
}),
@@ -122,7 +122,7 @@ describe('fetchLogsTree', () => {
}));
it('writes query to client', async () => {
- await fetchLogsTree(client, '', '0', resolver);
+ await fetchLogsTree(client, '/', '0', resolver);
expect(client.readQuery({ query: commitsQuery })).toEqual({
commits: [
expect.objectContaining({
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index 4db295fe0b7..cda47a5b0a5 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -1,4 +1,5 @@
export const simpleViewerMock = {
+ __typename: 'RepositoryBlob',
id: '1',
name: 'some_file.js',
size: 123,
diff --git a/spec/frontend/repository/utils/commit_spec.js b/spec/frontend/repository/utils/commit_spec.js
index b3dd5118308..65728e9cb24 100644
--- a/spec/frontend/repository/utils/commit_spec.js
+++ b/spec/frontend/repository/utils/commit_spec.js
@@ -15,7 +15,7 @@ const mockData = [
describe('normalizeData', () => {
it('normalizes data into LogTreeCommit object', () => {
- expect(normalizeData(mockData, '')).toEqual([
+ expect(normalizeData(mockData, '/')).toEqual([
{
sha: '123',
message: 'testing message',
diff --git a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
index 509681c5a77..7ab4aeee9bc 100644
--- a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
+++ b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
@@ -164,7 +164,7 @@ describe('AdminRunnerShowApp', () => {
});
});
- describe('when runner does not have an edit url ', () => {
+ describe('when runner does not have an edit url', () => {
beforeEach(async () => {
mockRunnerQueryResult({
editAdminUrl: null,
diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
index 97341be7d5d..55a298e1695 100644
--- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
@@ -17,6 +17,7 @@ import { updateHistory } from '~/lib/utils/url_utility';
import { upgradeStatusTokenConfig } from 'ee_else_ce/runner/components/search_tokens/upgrade_status_token_config';
import { createLocalState } from '~/runner/graphql/list/local_state';
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
+import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
@@ -33,6 +34,12 @@ import {
CREATED_ASC,
CREATED_DESC,
DEFAULT_SORT,
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
+ I18N_INSTANCE_TYPE,
+ I18N_GROUP_TYPE,
+ I18N_PROJECT_TYPE,
INSTANCE_TYPE,
PARAM_KEY_PAUSED,
PARAM_KEY_STATUS,
@@ -80,6 +87,7 @@ describe('AdminRunnersApp', () => {
let localMutations;
let showToast;
+ const findRunnerStackedLayoutBanner = () => wrapper.findComponent(RunnerStackedLayoutBanner);
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
@@ -139,6 +147,11 @@ describe('AdminRunnersApp', () => {
wrapper.destroy();
});
+ it('shows the feedback banner', () => {
+ createComponent();
+ expect(findRunnerStackedLayoutBanner().exists()).toBe(true);
+ });
+
it('shows the runner setup instructions', () => {
createComponent();
@@ -156,21 +169,16 @@ describe('AdminRunnersApp', () => {
});
it('shows the runner tabs', () => {
- expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
- `All ${mockRunnersCount} Instance ${mockRunnersCount} Group ${mockRunnersCount} Project ${mockRunnersCount}`,
+ const tabs = findRunnerTypeTabs().text();
+ expect(tabs).toMatchInterpolatedText(
+ `All ${mockRunnersCount} ${I18N_INSTANCE_TYPE} ${mockRunnersCount} ${I18N_GROUP_TYPE} ${mockRunnersCount} ${I18N_PROJECT_TYPE} ${mockRunnersCount}`,
);
});
it('shows the total', () => {
- expect(findRunnerStats().text()).toContain(
- `${s__('Runners|Online runners')} ${mockRunnersCount}`,
- );
- expect(findRunnerStats().text()).toContain(
- `${s__('Runners|Offline runners')} ${mockRunnersCount}`,
- );
- expect(findRunnerStats().text()).toContain(
- `${s__('Runners|Stale runners')} ${mockRunnersCount}`,
- );
+ expect(findRunnerStats().text()).toContain(`${I18N_STATUS_ONLINE} ${mockRunnersCount}`);
+ expect(findRunnerStats().text()).toContain(`${I18N_STATUS_OFFLINE} ${mockRunnersCount}`);
+ expect(findRunnerStats().text()).toContain(`${I18N_STATUS_STALE} ${mockRunnersCount}`);
});
});
diff --git a/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js b/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js
new file mode 100644
index 00000000000..21ec9f61f37
--- /dev/null
+++ b/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js
@@ -0,0 +1,164 @@
+import { __ } from '~/locale';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import RunnerStackedSummaryCell from '~/runner/components/cells/runner_stacked_summary_cell.vue';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import RunnerTags from '~/runner/components/runner_tags.vue';
+import RunnerSummaryField from '~/runner/components/cells/runner_summary_field.vue';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+import { INSTANCE_TYPE, I18N_INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
+
+import { allRunnersData } from '../../mock_data';
+
+const mockRunner = allRunnersData.data.runners.nodes[0];
+
+describe('RunnerTypeCell', () => {
+ let wrapper;
+
+ const findLockIcon = () => wrapper.findByTestId('lock-icon');
+ const findRunnerTags = () => wrapper.findComponent(RunnerTags);
+ const findRunnerSummaryField = (icon) =>
+ wrapper.findAllComponents(RunnerSummaryField).filter((w) => w.props('icon') === icon)
+ .wrappers[0];
+
+ const createComponent = (runner, options) => {
+ wrapper = mountExtended(RunnerStackedSummaryCell, {
+ propsData: {
+ runner: {
+ ...mockRunner,
+ ...runner,
+ },
+ },
+ stubs: {
+ RunnerSummaryField,
+ },
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Displays the runner name as id and short token', () => {
+ expect(wrapper.text()).toContain(
+ `#${getIdFromGraphQLId(mockRunner.id)} (${mockRunner.shortSha})`,
+ );
+ });
+
+ it('Does not display the locked icon', () => {
+ expect(findLockIcon().exists()).toBe(false);
+ });
+
+ it('Displays the locked icon for locked runners', () => {
+ createComponent({
+ runnerType: PROJECT_TYPE,
+ locked: true,
+ });
+
+ expect(findLockIcon().exists()).toBe(true);
+ });
+
+ it('Displays the runner type', () => {
+ createComponent({
+ runnerType: INSTANCE_TYPE,
+ locked: true,
+ });
+
+ expect(wrapper.text()).toContain(I18N_INSTANCE_TYPE);
+ });
+
+ it('Displays the runner version', () => {
+ expect(wrapper.text()).toContain(mockRunner.version);
+ });
+
+ it('Displays the runner description', () => {
+ expect(wrapper.text()).toContain(mockRunner.description);
+ });
+
+ it('Displays last contact', () => {
+ createComponent({
+ contactedAt: '2022-01-02',
+ });
+
+ expect(findRunnerSummaryField('clock').find(TimeAgo).props('time')).toBe('2022-01-02');
+ });
+
+ it('Displays empty last contact', () => {
+ createComponent({
+ contactedAt: null,
+ });
+
+ expect(findRunnerSummaryField('clock').find(TimeAgo).exists()).toBe(false);
+ expect(findRunnerSummaryField('clock').text()).toContain(__('Never'));
+ });
+
+ it('Displays ip address', () => {
+ createComponent({
+ ipAddress: '127.0.0.1',
+ });
+
+ expect(findRunnerSummaryField('disk').text()).toContain('127.0.0.1');
+ });
+
+ it('Displays no ip address', () => {
+ createComponent({
+ ipAddress: null,
+ });
+
+ expect(findRunnerSummaryField('disk')).toBeUndefined();
+ });
+
+ it('Displays job count', () => {
+ expect(findRunnerSummaryField('pipeline').text()).toContain(`${mockRunner.jobCount}`);
+ });
+
+ it('Formats large job counts', () => {
+ createComponent({
+ jobCount: 1000,
+ });
+
+ expect(findRunnerSummaryField('pipeline').text()).toContain('1,000');
+ });
+
+ it('Formats large job counts with a plus symbol', () => {
+ createComponent({
+ jobCount: 1001,
+ });
+
+ expect(findRunnerSummaryField('pipeline').text()).toContain('1,000+');
+ });
+
+ it('Displays created at', () => {
+ expect(findRunnerSummaryField('calendar').find(TimeAgo).props('time')).toBe(
+ mockRunner.createdAt,
+ );
+ });
+
+ it('Displays tag list', () => {
+ createComponent({
+ tagList: ['shell', 'linux'],
+ });
+
+ expect(findRunnerTags().props('tagList')).toEqual(['shell', 'linux']);
+ });
+
+ it('Displays a custom slot', () => {
+ const slotContent = 'My custom runner name';
+
+ createComponent(
+ {},
+ {
+ slots: {
+ 'runner-name': slotContent,
+ },
+ },
+ );
+
+ expect(wrapper.text()).toContain(slotContent);
+ });
+});
diff --git a/spec/frontend/runner/components/cells/runner_status_cell_spec.js b/spec/frontend/runner/components/cells/runner_status_cell_spec.js
index 0f5133d0ae2..1d4e3762c91 100644
--- a/spec/frontend/runner/components/cells/runner_status_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_status_cell_spec.js
@@ -3,7 +3,14 @@ import RunnerStatusCell from '~/runner/components/cells/runner_status_cell.vue';
import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import RunnerPausedBadge from '~/runner/components/runner_paused_badge.vue';
-import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE } from '~/runner/constants';
+import {
+ I18N_PAUSED,
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ INSTANCE_TYPE,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+} from '~/runner/constants';
describe('RunnerStatusCell', () => {
let wrapper;
@@ -31,8 +38,8 @@ describe('RunnerStatusCell', () => {
it('Displays online status', () => {
createComponent();
- expect(wrapper.text()).toMatchInterpolatedText('online');
- expect(findStatusBadge().text()).toBe('online');
+ expect(wrapper.text()).toContain(I18N_STATUS_ONLINE);
+ expect(findStatusBadge().text()).toBe(I18N_STATUS_ONLINE);
});
it('Displays offline status', () => {
@@ -42,8 +49,8 @@ describe('RunnerStatusCell', () => {
},
});
- expect(wrapper.text()).toMatchInterpolatedText('offline');
- expect(findStatusBadge().text()).toBe('offline');
+ expect(wrapper.text()).toMatchInterpolatedText(I18N_STATUS_OFFLINE);
+ expect(findStatusBadge().text()).toBe(I18N_STATUS_OFFLINE);
});
it('Displays paused status', () => {
@@ -54,8 +61,8 @@ describe('RunnerStatusCell', () => {
},
});
- expect(wrapper.text()).toMatchInterpolatedText('online paused');
- expect(findPausedBadge().text()).toBe('paused');
+ expect(wrapper.text()).toMatchInterpolatedText(`${I18N_STATUS_ONLINE} ${I18N_PAUSED}`);
+ expect(findPausedBadge().text()).toBe(I18N_PAUSED);
});
it('Is empty when data is missing', () => {
diff --git a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
deleted file mode 100644
index b06ab652212..00000000000
--- a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import { __ } from '~/locale';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue';
-import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
-
-const mockId = '1';
-const mockShortSha = '2P6oDVDm';
-const mockDescription = 'runner-1';
-const mockIpAddress = '0.0.0.0';
-
-describe('RunnerTypeCell', () => {
- let wrapper;
-
- const findLockIcon = () => wrapper.findByTestId('lock-icon');
-
- const createComponent = (runner, options) => {
- wrapper = mountExtended(RunnerSummaryCell, {
- propsData: {
- runner: {
- id: `gid://gitlab/Ci::Runner/${mockId}`,
- shortSha: mockShortSha,
- description: mockDescription,
- ipAddress: mockIpAddress,
- runnerType: INSTANCE_TYPE,
- ...runner,
- },
- },
- ...options,
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('Displays the runner name as id and short token', () => {
- expect(wrapper.text()).toContain(`#${mockId} (${mockShortSha})`);
- });
-
- it('Displays the runner type', () => {
- expect(wrapper.text()).toContain('shared');
- });
-
- it('Does not display the locked icon', () => {
- expect(findLockIcon().exists()).toBe(false);
- });
-
- it('Displays the locked icon for locked runners', () => {
- createComponent({
- runnerType: PROJECT_TYPE,
- locked: true,
- });
-
- expect(findLockIcon().exists()).toBe(true);
- });
-
- it('Displays the runner description', () => {
- expect(wrapper.text()).toContain(mockDescription);
- });
-
- it('Displays ip address', () => {
- expect(wrapper.text()).toContain(`${__('IP Address')} ${mockIpAddress}`);
- });
-
- it('Displays no ip address', () => {
- createComponent({
- ipAddress: null,
- });
-
- expect(wrapper.text()).not.toContain(__('IP Address'));
- });
-
- it('Displays a custom slot', () => {
- const slotContent = 'My custom runner summary';
-
- createComponent(
- {},
- {
- slots: {
- 'runner-name': slotContent,
- },
- },
- );
-
- expect(wrapper.text()).toContain(slotContent);
- });
-});
diff --git a/spec/frontend/runner/components/cells/runner_summary_field_spec.js b/spec/frontend/runner/components/cells/runner_summary_field_spec.js
new file mode 100644
index 00000000000..b49addf112f
--- /dev/null
+++ b/spec/frontend/runner/components/cells/runner_summary_field_spec.js
@@ -0,0 +1,49 @@
+import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerSummaryField from '~/runner/components/cells/runner_summary_field.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+describe('RunnerSummaryField', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.findComponent(GlIcon);
+ const getTooltipValue = () => getBinding(wrapper.element, 'gl-tooltip').value;
+
+ const createComponent = ({ props, ...options } = {}) => {
+ wrapper = shallowMount(RunnerSummaryField, {
+ propsData: {
+ icon: '',
+ tooltip: '',
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ ...options,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('shows content in slot', () => {
+ createComponent({
+ slots: { default: 'content' },
+ });
+
+ expect(wrapper.text()).toBe('content');
+ });
+
+ it('shows icon', () => {
+ createComponent({ props: { icon: 'git' } });
+
+ expect(findIcon().props('name')).toBe('git');
+ });
+
+ it('shows tooltip', () => {
+ createComponent({ props: { tooltip: 'tooltip' } });
+
+ expect(getTooltipValue()).toBe('tooltip');
+ });
+});
diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js
index 552ee29b6f9..f2281223a25 100644
--- a/spec/frontend/runner/components/runner_details_spec.js
+++ b/spec/frontend/runner/components/runner_details_spec.js
@@ -25,7 +25,12 @@ describe('RunnerDetails', () => {
const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
- const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
+ const createComponent = ({
+ props = {},
+ stubs,
+ mountFn = shallowMountExtended,
+ enforceRunnerTokenExpiresAt = false,
+ } = {}) => {
wrapper = mountFn(RunnerDetails, {
propsData: {
...props,
@@ -34,6 +39,9 @@ describe('RunnerDetails', () => {
RunnerDetail,
...stubs,
},
+ provide: {
+ glFeatures: { enforceRunnerTokenExpiresAt },
+ },
});
};
@@ -63,6 +71,8 @@ describe('RunnerDetails', () => {
${'Maximum job timeout'} | ${{ maximumTimeout: 0 }} | ${'0 seconds'}
${'Maximum job timeout'} | ${{ maximumTimeout: 59 }} | ${'59 seconds'}
${'Maximum job timeout'} | ${{ maximumTimeout: 10 * 60 + 5 }} | ${'10 minutes 5 seconds'}
+ ${'Token expiry'} | ${{ tokenExpiresAt: mockOneHourAgo }} | ${'1 hour ago'}
+ ${'Token expiry'} | ${{ tokenExpiresAt: null }} | ${'Never expires'}
`('"$field" field', ({ field, runner, expectedValue }) => {
beforeEach(() => {
createComponent({
@@ -72,6 +82,7 @@ describe('RunnerDetails', () => {
...runner,
},
},
+ enforceRunnerTokenExpiresAt: true,
stubs: {
GlIntersperse,
GlSprintf,
@@ -124,5 +135,22 @@ describe('RunnerDetails', () => {
expect(findDetailGroups().props('runner')).toEqual(mockGroupRunner);
});
});
+
+ describe('Token expiration field', () => {
+ it.each`
+ case | flag | shown
+ ${'is shown when feature flag is enabled'} | ${true} | ${true}
+ ${'is not shown when feature flag is disabled'} | ${false} | ${false}
+ `('$case', ({ flag, shown }) => {
+ createComponent({
+ props: {
+ runner: mockGroupRunner,
+ },
+ enforceRunnerTokenExpiresAt: flag,
+ });
+
+ expect(findDd('Token expiry', wrapper).exists()).toBe(shown);
+ });
+ });
});
});
diff --git a/spec/frontend/runner/components/runner_header_spec.js b/spec/frontend/runner/components/runner_header_spec.js
index 8799c218b06..701d39108cb 100644
--- a/spec/frontend/runner/components/runner_header_spec.js
+++ b/spec/frontend/runner/components/runner_header_spec.js
@@ -1,6 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
+import { I18N_STATUS_ONLINE, I18N_GROUP_TYPE, GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -49,7 +49,7 @@ describe('RunnerHeader', () => {
},
});
- expect(findRunnerStatusBadge().text()).toContain('online');
+ expect(findRunnerStatusBadge().text()).toContain(I18N_STATUS_ONLINE);
});
it('displays the runner type', () => {
@@ -60,7 +60,7 @@ describe('RunnerHeader', () => {
},
});
- expect(findRunnerTypeBadge().text()).toContain('group');
+ expect(findRunnerTypeBadge().text()).toContain(I18N_GROUP_TYPE);
});
it('displays the runner id', () => {
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index 7b58a81bb0d..54a9e713721 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -7,6 +7,7 @@ import {
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
+import { I18N_PROJECT_TYPE, I18N_STATUS_NEVER_CONTACTED } from '~/runner/constants';
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
const mockRunners = allRunnersData.data.runners.nodes;
@@ -22,7 +23,10 @@ describe('RunnerList', () => {
const findCell = ({ row = 0, fieldKey }) =>
extendedWrapper(findRows().at(row).find(`[data-testid="td-${fieldKey}"]`));
- const createComponent = ({ props = {}, ...options } = {}, mountFn = shallowMountExtended) => {
+ const createComponent = (
+ { props = {}, provide = {}, ...options } = {},
+ mountFn = shallowMountExtended,
+ ) => {
wrapper = mountFn(RunnerList, {
propsData: {
runners: mockRunners,
@@ -32,6 +36,7 @@ describe('RunnerList', () => {
provide: {
onlineContactTimeoutSecs,
staleTimeoutSecs,
+ ...provide,
},
...options,
});
@@ -60,10 +65,6 @@ describe('RunnerList', () => {
expect(headerLabels).toEqual([
'Status',
'Runner',
- 'Version',
- 'Jobs',
- 'Tags',
- 'Last contact',
'', // actions has no label
]);
});
@@ -83,24 +84,28 @@ describe('RunnerList', () => {
});
it('Displays details of a runner', () => {
- const { id, description, version, shortSha } = mockRunners[0];
-
createComponent({}, mountExtended);
+ const { id, description, version, shortSha } = mockRunners[0];
+ const numericId = getIdFromGraphQLId(id);
+
// Badges
- expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('never contacted');
+ expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText(
+ I18N_STATUS_NEVER_CONTACTED,
+ );
// Runner summary
- expect(findCell({ fieldKey: 'summary' }).text()).toContain(
- `#${getIdFromGraphQLId(id)} (${shortSha})`,
- );
- expect(findCell({ fieldKey: 'summary' }).text()).toContain(description);
+ const summary = findCell({ fieldKey: 'summary' }).text();
- // Other fields
- expect(findCell({ fieldKey: 'version' }).text()).toBe(version);
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('0');
- expect(findCell({ fieldKey: 'tagList' }).text()).toBe('');
- expect(findCell({ fieldKey: 'contactedAt' }).text()).toEqual(expect.any(String));
+ expect(summary).toContain(`#${numericId} (${shortSha})`);
+ expect(summary).toContain(I18N_PROJECT_TYPE);
+
+ expect(summary).toContain(version);
+ expect(summary).toContain(description);
+
+ expect(summary).toContain('Last contact');
+ expect(summary).toContain('0'); // job count
+ expect(summary).toContain('Created');
// Actions
expect(findCell({ fieldKey: 'actions' }).exists()).toBe(true);
@@ -159,42 +164,6 @@ describe('RunnerList', () => {
});
});
- describe('Table data formatting', () => {
- let mockRunnersCopy;
-
- beforeEach(() => {
- mockRunnersCopy = [
- {
- ...mockRunners[0],
- },
- ];
- });
-
- it('Formats job counts', () => {
- mockRunnersCopy[0].jobCount = 1;
-
- createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
-
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1');
- });
-
- it('Formats large job counts', () => {
- mockRunnersCopy[0].jobCount = 1000;
-
- createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
-
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000');
- });
-
- it('Formats large job counts with a plus symbol', () => {
- mockRunnersCopy[0].jobCount = 1001;
-
- createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
-
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000+');
- });
- });
-
it('Shows runner identifier', () => {
const { id, shortSha } = mockRunners[0];
const numericId = getIdFromGraphQLId(id);
diff --git a/spec/frontend/runner/components/runner_paused_badge_spec.js b/spec/frontend/runner/components/runner_paused_badge_spec.js
index 18cfcfae864..c1c7351aab2 100644
--- a/spec/frontend/runner/components/runner_paused_badge_spec.js
+++ b/spec/frontend/runner/components/runner_paused_badge_spec.js
@@ -2,6 +2,7 @@ import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStatePausedBadge from '~/runner/components/runner_paused_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { I18N_PAUSED } from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
@@ -29,8 +30,8 @@ describe('RunnerTypeBadge', () => {
});
it('renders paused state', () => {
- expect(wrapper.text()).toBe('paused');
- expect(findBadge().props('variant')).toBe('danger');
+ expect(wrapper.text()).toBe(I18N_PAUSED);
+ expect(findBadge().props('variant')).toBe('warning');
});
it('renders tooltip', () => {
diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js
index c988fb8477d..eca042cae86 100644
--- a/spec/frontend/runner/components/runner_projects_spec.js
+++ b/spec/frontend/runner/components/runner_projects_spec.js
@@ -1,4 +1,4 @@
-import { GlSkeletonLoader } from '@gitlab/ui';
+import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -8,7 +8,9 @@ import { createAlert } from '~/flash';
import { sprintf } from '~/locale';
import {
I18N_ASSIGNED_PROJECTS,
- I18N_NONE,
+ I18N_CLEAR_FILTER_PROJECTS,
+ I18N_FILTER_PROJECTS,
+ I18N_NO_PROJECTS_FOUND,
RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
} from '~/runner/constants';
import RunnerProjects from '~/runner/components/runner_projects.vue';
@@ -35,6 +37,7 @@ describe('RunnerProjects', () => {
const findHeading = () => wrapper.find('h3');
const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader);
+ const findGlSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem);
const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
@@ -64,10 +67,21 @@ describe('RunnerProjects', () => {
expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
expect(mockRunnerProjectsQuery).toHaveBeenCalledWith({
id: mockRunner.id,
+ search: '',
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
});
});
+ it('Shows a filter box', () => {
+ createComponent();
+
+ expect(findGlSearchBoxByType().attributes()).toMatchObject({
+ clearbuttontitle: I18N_CLEAR_FILTER_PROJECTS,
+ debounce: '500',
+ placeholder: I18N_FILTER_PROJECTS,
+ });
+ });
+
describe('When there are projects assigned', () => {
beforeEach(async () => {
mockRunnerProjectsQuery.mockResolvedValueOnce(runnerProjectsData);
@@ -110,6 +124,7 @@ describe('RunnerProjects', () => {
expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2);
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
id: mockRunner.id,
+ search: '',
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
after: 'AFTER_CURSOR',
});
@@ -123,10 +138,51 @@ describe('RunnerProjects', () => {
expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3);
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
id: mockRunner.id,
+ search: '',
last: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
before: 'BEFORE_CURSOR',
});
});
+
+ it('When user filters after paginating, the first page is requested', async () => {
+ findGlSearchBoxByType().vm.$emit('input', 'my search');
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3);
+ expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ search: 'my search',
+ first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ });
+ });
+ });
+
+ describe('When user filters', () => {
+ it('Filtered results are requested', async () => {
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+
+ findGlSearchBoxByType().vm.$emit('input', 'my search');
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2);
+ expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ search: 'my search',
+ first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ });
+ });
+
+ it('Filtered results are not requested for short searches', async () => {
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+
+ findGlSearchBoxByType().vm.$emit('input', 'm');
+ await waitForPromises();
+
+ findGlSearchBoxByType().vm.$emit('input', 'my');
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+ });
});
});
@@ -136,10 +192,11 @@ describe('RunnerProjects', () => {
expect(findGlSkeletonLoading().exists()).toBe(true);
- expect(wrapper.findByText(I18N_NONE).exists()).toBe(false);
+ expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(false);
expect(findRunnerAssignedItems().length).toBe(0);
expect(findRunnerPagination().attributes('disabled')).toBe('true');
+ expect(findGlSearchBoxByType().props('isLoading')).toBe(true);
});
});
@@ -168,7 +225,7 @@ describe('RunnerProjects', () => {
});
it('Shows a "None" label', () => {
- expect(wrapper.findByText(I18N_NONE).exists()).toBe(true);
+ expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(true);
});
});
diff --git a/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js b/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
new file mode 100644
index 00000000000..1a8aced9292
--- /dev/null
+++ b/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
@@ -0,0 +1,39 @@
+import { nextTick } from 'vue';
+import { GlBanner } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+
+describe('RunnerStackedLayoutBanner', () => {
+ let wrapper;
+
+ const findBanner = () => wrapper.findComponent(GlBanner);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
+
+ const createComponent = ({ ...options } = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(RunnerStackedLayoutBanner, {
+ ...options,
+ });
+ };
+
+ it('Displays a banner', () => {
+ createComponent();
+
+ expect(findBanner().props()).toMatchObject({
+ svgPath: expect.stringContaining('data:image/svg+xml;utf8,'),
+ title: expect.any(String),
+ buttonText: expect.any(String),
+ buttonLink: expect.stringContaining('https://gitlab.com/gitlab-org/gitlab/-/issues/'),
+ });
+ expect(findLocalStorageSync().exists()).toBe(true);
+ });
+
+ it('Does not display a banner when dismissed', async () => {
+ findLocalStorageSync().vm.$emit('input', true);
+
+ await nextTick();
+
+ expect(findBanner().exists()).toBe(false);
+ expect(findLocalStorageSync().exists()).toBe(true); // continues syncing after removal
+ });
+});
diff --git a/spec/frontend/runner/components/runner_status_badge_spec.js b/spec/frontend/runner/components/runner_status_badge_spec.js
index bb833bd7d5a..9ab6378304f 100644
--- a/spec/frontend/runner/components/runner_status_badge_spec.js
+++ b/spec/frontend/runner/components/runner_status_badge_spec.js
@@ -3,12 +3,16 @@ import { shallowMount } from '@vue/test-utils';
import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import {
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_NEVER_CONTACTED,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
+ I18N_NEVER_CONTACTED_TOOLTIP,
+ I18N_STALE_NEVER_CONTACTED_TOOLTIP,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
STATUS_NEVER_CONTACTED,
- I18N_NEVER_CONTACTED_TOOLTIP,
- I18N_STALE_NEVER_CONTACTED_TOOLTIP,
} from '~/runner/constants';
describe('RunnerTypeBadge', () => {
@@ -46,7 +50,7 @@ describe('RunnerTypeBadge', () => {
it('renders online state', () => {
createComponent();
- expect(wrapper.text()).toBe('online');
+ expect(wrapper.text()).toBe(I18N_STATUS_ONLINE);
expect(findBadge().props('variant')).toBe('success');
expect(getTooltip().value).toBe('Runner is online; last contact was 1 minute ago');
});
@@ -59,7 +63,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('never contacted');
+ expect(wrapper.text()).toBe(I18N_STATUS_NEVER_CONTACTED);
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe(I18N_NEVER_CONTACTED_TOOLTIP);
});
@@ -72,7 +76,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('offline');
+ expect(wrapper.text()).toBe(I18N_STATUS_OFFLINE);
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe('Runner is offline; last contact was 1 day ago');
});
@@ -85,7 +89,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('stale');
+ expect(wrapper.text()).toBe(I18N_STATUS_STALE);
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe('Runner is stale; last contact was 1 year ago');
});
@@ -98,7 +102,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('stale');
+ expect(wrapper.text()).toBe(I18N_STATUS_STALE);
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe(I18N_STALE_NEVER_CONTACTED_TOOLTIP);
});
@@ -112,7 +116,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('online');
+ expect(wrapper.text()).toBe(I18N_STATUS_ONLINE);
expect(getTooltip().value).toBe('Runner is online; last contact was never');
});
diff --git a/spec/frontend/runner/components/runner_tag_spec.js b/spec/frontend/runner/components/runner_tag_spec.js
index bd05d4b2cfe..391c17f81cb 100644
--- a/spec/frontend/runner/components/runner_tag_spec.js
+++ b/spec/frontend/runner/components/runner_tag_spec.js
@@ -1,6 +1,8 @@
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+
+import { RUNNER_TAG_BADGE_VARIANT } from '~/runner/constants';
import RunnerTag from '~/runner/components/runner_tag.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -48,7 +50,7 @@ describe('RunnerTag', () => {
it('Displays tags with correct style', () => {
expect(findBadge().props()).toMatchObject({
size: 'sm',
- variant: 'neutral',
+ variant: RUNNER_TAG_BADGE_VARIANT,
});
});
diff --git a/spec/frontend/runner/components/runner_tags_spec.js b/spec/frontend/runner/components/runner_tags_spec.js
index da89a659432..c6bfabdb18a 100644
--- a/spec/frontend/runner/components/runner_tags_spec.js
+++ b/spec/frontend/runner/components/runner_tags_spec.js
@@ -34,7 +34,6 @@ describe('RunnerTags', () => {
it('Displays tags with correct style', () => {
expect(findBadge().props('size')).toBe('sm');
- expect(findBadge().props('variant')).toBe('neutral');
});
it('Displays tags with md size', () => {
@@ -50,7 +49,6 @@ describe('RunnerTags', () => {
props: { tagList: null },
});
- expect(wrapper.text()).toBe('');
- expect(findBadge().exists()).toBe(false);
+ expect(wrapper.html()).toEqual('');
});
});
diff --git a/spec/frontend/runner/components/runner_type_badge_spec.js b/spec/frontend/runner/components/runner_type_badge_spec.js
index 7bb0a2e6e2f..fe922fb9d18 100644
--- a/spec/frontend/runner/components/runner_type_badge_spec.js
+++ b/spec/frontend/runner/components/runner_type_badge_spec.js
@@ -2,7 +2,14 @@ import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
+import {
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ I18N_INSTANCE_TYPE,
+ I18N_GROUP_TYPE,
+ I18N_PROJECT_TYPE,
+} from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
@@ -27,9 +34,9 @@ describe('RunnerTypeBadge', () => {
describe.each`
type | text
- ${INSTANCE_TYPE} | ${'shared'}
- ${GROUP_TYPE} | ${'group'}
- ${PROJECT_TYPE} | ${'specific'}
+ ${INSTANCE_TYPE} | ${I18N_INSTANCE_TYPE}
+ ${GROUP_TYPE} | ${I18N_GROUP_TYPE}
+ ${PROJECT_TYPE} | ${I18N_PROJECT_TYPE}
`('displays $type runner', ({ type, text }) => {
beforeEach(() => {
createComponent({ props: { type } });
@@ -37,7 +44,7 @@ describe('RunnerTypeBadge', () => {
it(`as "${text}" with an "info" variant`, () => {
expect(findBadge().text()).toBe(text);
- expect(findBadge().props('variant')).toBe('info');
+ expect(findBadge().props('variant')).toBe('muted');
});
it('with a tooltip', () => {
diff --git a/spec/frontend/runner/components/runner_type_tabs_spec.js b/spec/frontend/runner/components/runner_type_tabs_spec.js
index 22d2a9e60f7..45ab8684332 100644
--- a/spec/frontend/runner/components/runner_type_tabs_spec.js
+++ b/spec/frontend/runner/components/runner_type_tabs_spec.js
@@ -28,7 +28,7 @@ const mockCount = (type, multiplier = 1) => {
describe('RunnerTypeTabs', () => {
let wrapper;
- const findTabs = () => wrapper.findAll(GlTab);
+ const findTabs = () => wrapper.findAllComponents(GlTab);
const findActiveTab = () =>
findTabs()
.filter((tab) => tab.attributes('active') === 'true')
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index 3037364d941..7b67a89f989 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -1,6 +1,7 @@
import Vue, { nextTick } from 'vue';
import { GlForm, GlSkeletonLoader } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
+import { __ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -47,6 +48,7 @@ describe('RunnerUpdateForm', () => {
const findSubmit = () => wrapper.find('[type="submit"]');
const findSubmitDisabledAttr = () => findSubmit().attributes('disabled');
+ const findCancelBtn = () => wrapper.findByRole('link', { name: __('Cancel') });
const submitForm = () => findForm().trigger('submit');
const submitFormAndWait = () => submitForm().then(waitForPromises);
@@ -117,6 +119,11 @@ describe('RunnerUpdateForm', () => {
expect(mockRunner).toMatchObject(getFieldsModel());
});
+ it('Form shows a cancel button', () => {
+ expect(runnerUpdateHandler).not.toHaveBeenCalled();
+ expect(findCancelBtn().attributes('href')).toBe(mockRunnerPath);
+ });
+
it('Form prevent multiple submissions', async () => {
await submitForm();
diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js
index 7f1f22be94f..4afbe453903 100644
--- a/spec/frontend/runner/components/stat/runner_stats_spec.js
+++ b/spec/frontend/runner/components/stat/runner_stats_spec.js
@@ -1,13 +1,20 @@
import { shallowMount, mount } from '@vue/test-utils';
-import { s__ } from '~/locale';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
-import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
+import {
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
+ INSTANCE_TYPE,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
+} from '~/runner/constants';
describe('RunnerStats', () => {
let wrapper;
- const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat).wrappers;
+ const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat);
const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(RunnerStats, {
@@ -46,16 +53,28 @@ describe('RunnerStats', () => {
});
const text = wrapper.text();
- expect(text).toMatch(`${s__('Runners|Online runners')} 3`);
- expect(text).toMatch(`${s__('Runners|Offline runners')} 2`);
- expect(text).toMatch(`${s__('Runners|Stale runners')} 1`);
+ expect(text).toContain(`${I18N_STATUS_ONLINE} 3`);
+ expect(text).toContain(`${I18N_STATUS_OFFLINE} 2`);
+ expect(text).toContain(`${I18N_STATUS_STALE} 1`);
+ });
+
+ it('Skips query for other stats', () => {
+ createComponent({
+ props: {
+ variables: { status: STATUS_ONLINE },
+ },
+ });
+
+ expect(findSingleStats().at(0).props('skip')).toBe(false);
+ expect(findSingleStats().at(1).props('skip')).toBe(true);
+ expect(findSingleStats().at(2).props('skip')).toBe(true);
});
it('Displays all counts for filtered searches', () => {
const mockVariables = { paused: true };
createComponent({ props: { variables: mockVariables } });
- findSingleStats().forEach((stat) => {
+ findSingleStats().wrappers.forEach((stat) => {
expect(stat.props('variables')).toMatchObject(mockVariables);
});
});
diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js
index 57d64202219..a17502c7eec 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -15,6 +15,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
import { upgradeStatusTokenConfig } from 'ee_else_ce/runner/components/search_tokens/upgrade_status_token_config';
+import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
@@ -28,6 +29,9 @@ import {
CREATED_ASC,
CREATED_DESC,
DEFAULT_SORT,
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
INSTANCE_TYPE,
GROUP_TYPE,
PARAM_KEY_PAUSED,
@@ -74,6 +78,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('GroupRunnersApp', () => {
let wrapper;
+ const findRunnerStackedLayoutBanner = () => wrapper.findComponent(RunnerStackedLayoutBanner);
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
@@ -122,6 +127,11 @@ describe('GroupRunnersApp', () => {
wrapper.destroy();
});
+ it('shows the feedback banner', () => {
+ createComponent();
+ expect(findRunnerStackedLayoutBanner().exists()).toBe(true);
+ });
+
it('shows the runner tabs with a runner count for each type', async () => {
await createComponent({ mountFn: mountExtended });
@@ -153,15 +163,10 @@ describe('GroupRunnersApp', () => {
groupFullPath: mockGroupFullPath,
});
- expect(findRunnerStats().text()).toContain(
- `${s__('Runners|Online runners')} ${mockGroupRunnersCount}`,
- );
- expect(findRunnerStats().text()).toContain(
- `${s__('Runners|Offline runners')} ${mockGroupRunnersCount}`,
- );
- expect(findRunnerStats().text()).toContain(
- `${s__('Runners|Stale runners')} ${mockGroupRunnersCount}`,
- );
+ const text = findRunnerStats().text();
+ expect(text).toContain(`${I18N_STATUS_ONLINE} ${mockGroupRunnersCount}`);
+ expect(text).toContain(`${I18N_STATUS_OFFLINE} ${mockGroupRunnersCount}`);
+ expect(text).toContain(`${I18N_STATUS_STALE} ${mockGroupRunnersCount}`);
});
it('shows the runners list', async () => {
@@ -396,4 +401,36 @@ describe('GroupRunnersApp', () => {
});
});
});
+
+ describe('when user has permission to register group runner', () => {
+ beforeEach(() => {
+ createComponent({
+ propsData: {
+ registrationToken: mockRegistrationToken,
+ groupFullPath: mockGroupFullPath,
+ groupRunnersLimitedCount: mockGroupRunnersCount,
+ },
+ });
+ });
+
+ it('shows the register group runner button', () => {
+ expect(findRegistrationDropdown().exists()).toBe(true);
+ });
+ });
+
+ describe('when user has no permission to register group runner', () => {
+ beforeEach(() => {
+ createComponent({
+ propsData: {
+ registrationToken: null,
+ groupFullPath: mockGroupFullPath,
+ groupRunnersLimitedCount: mockGroupRunnersCount,
+ },
+ });
+ });
+
+ it('does not show the register group runner button', () => {
+ expect(findRegistrationDropdown().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js b/spec/frontend/runner/runner_edit/runner_edit_app_spec.js
index ffe3599ac64..fb118817d51 100644
--- a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
+++ b/spec/frontend/runner/runner_edit/runner_edit_app_spec.js
@@ -9,8 +9,9 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/runner/components/runner_header.vue';
import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
import runnerFormQuery from '~/runner/graphql/edit/runner_form.query.graphql';
-import AdminRunnerEditApp from '~//runner/admin_runner_edit/admin_runner_edit_app.vue';
+import RunnerEditApp from '~//runner/runner_edit/runner_edit_app.vue';
import { captureException } from '~/runner/sentry_utils';
+import { I18N_STATUS_NEVER_CONTACTED, I18N_INSTANCE_TYPE } from '~/runner/constants';
import { runnerFormData } from '../mock_data';
@@ -24,7 +25,7 @@ const mockRunnerPath = `/admin/runners/${mockRunnerId}`;
Vue.use(VueApollo);
-describe('AdminRunnerEditApp', () => {
+describe('RunnerEditApp', () => {
let wrapper;
let mockRunnerQuery;
@@ -32,7 +33,7 @@ describe('AdminRunnerEditApp', () => {
const findRunnerUpdateForm = () => wrapper.findComponent(RunnerUpdateForm);
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
- wrapper = mountFn(AdminRunnerEditApp, {
+ wrapper = mountFn(RunnerEditApp, {
apolloProvider: createMockApollo([[runnerFormQuery, mockRunnerQuery]]),
propsData: {
runnerId: mockRunnerId,
@@ -69,8 +70,8 @@ describe('AdminRunnerEditApp', () => {
it('displays the runner type and status', async () => {
await createComponentWithApollo({ mountFn: mount });
- expect(findRunnerHeader().text()).toContain(`never contacted`);
- expect(findRunnerHeader().text()).toContain(`shared`);
+ expect(findRunnerHeader().text()).toContain(I18N_STATUS_NEVER_CONTACTED);
+ expect(findRunnerHeader().text()).toContain(I18N_INSTANCE_TYPE);
});
it('displays a loading runner form', () => {
@@ -102,7 +103,7 @@ describe('AdminRunnerEditApp', () => {
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
- component: 'AdminRunnerEditApp',
+ component: 'RunnerEditApp',
});
});
diff --git a/spec/frontend/runner/utils_spec.js b/spec/frontend/runner/utils_spec.js
index 1db9815dfd8..33de1345f85 100644
--- a/spec/frontend/runner/utils_spec.js
+++ b/spec/frontend/runner/utils_spec.js
@@ -1,4 +1,4 @@
-import { formatJobCount, tableField, getPaginationVariables } from '~/runner/utils';
+import { formatJobCount, tableField, getPaginationVariables, parseInterval } from '~/runner/utils';
describe('~/runner/utils', () => {
describe('formatJobCount', () => {
@@ -66,4 +66,15 @@ describe('~/runner/utils', () => {
expect(getPaginationVariables(pagination, pageSize)).toEqual(variables);
});
});
+
+ describe('parseInterval', () => {
+ it.each`
+ case | argument | returnValue
+ ${'parses integer'} | ${'86400'} | ${86400}
+ ${'returns null for undefined'} | ${undefined} | ${null}
+ ${'returns null for null'} | ${null} | ${null}
+ `('$case', ({ argument, returnValue }) => {
+ expect(parseInterval(argument)).toStrictEqual(returnValue);
+ });
+ });
});
diff --git a/spec/frontend/search/sidebar/components/radio_filter_spec.js b/spec/frontend/search/sidebar/components/radio_filter_spec.js
index 39d5ee581ec..c0a8259b4fe 100644
--- a/spec/frontend/search/sidebar/components/radio_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/radio_filter_spec.js
@@ -44,7 +44,7 @@ describe('RadioFilter', () => {
});
const findGlRadioButtonGroup = () => wrapper.find(GlFormRadioGroup);
- const findGlRadioButtons = () => findGlRadioButtonGroup().findAll(GlFormRadio);
+ const findGlRadioButtons = () => findGlRadioButtonGroup().findAllComponents(GlFormRadio);
const findGlRadioButtonsText = () => findGlRadioButtons().wrappers.map((w) => w.text());
describe('template', () => {
diff --git a/spec/frontend/search/sort/components/app_spec.js b/spec/frontend/search/sort/components/app_spec.js
index 04520a3e704..0e8eebba3cb 100644
--- a/spec/frontend/search/sort/components/app_spec.js
+++ b/spec/frontend/search/sort/components/app_spec.js
@@ -46,7 +46,7 @@ describe('GlobalSearchSort', () => {
const findSortButtonGroup = () => wrapper.find(GlButtonGroup);
const findSortDropdown = () => wrapper.find(GlDropdown);
const findSortDirectionButton = () => wrapper.find(GlButton);
- const findDropdownItems = () => findSortDropdown().findAll(GlDropdownItem);
+ const findDropdownItems = () => findSortDropdown().findAllComponents(GlDropdownItem);
const findDropdownItemsText = () => findDropdownItems().wrappers.map((w) => w.text());
describe('template', () => {
diff --git a/spec/frontend/set_status_modal/set_status_form_spec.js b/spec/frontend/set_status_modal/set_status_form_spec.js
new file mode 100644
index 00000000000..8e1623eedf5
--- /dev/null
+++ b/spec/frontend/set_status_modal/set_status_form_spec.js
@@ -0,0 +1,167 @@
+import $ from 'jquery';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import SetStatusForm from '~/set_status_modal/set_status_form.vue';
+import EmojiPicker from '~/emoji/components/picker.vue';
+import { timeRanges } from '~/vue_shared/constants';
+import { sprintf } from '~/locale';
+import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
+
+describe('SetStatusForm', () => {
+ let wrapper;
+
+ const defaultPropsData = {
+ defaultEmoji: 'speech_balloon',
+ emoji: 'thumbsup',
+ message: 'Foo bar',
+ availability: false,
+ };
+
+ const createComponent = async ({ propsData = {} } = {}) => {
+ wrapper = mountExtended(SetStatusForm, {
+ propsData: {
+ ...defaultPropsData,
+ ...propsData,
+ },
+ });
+
+ await waitForPromises();
+ };
+
+ const findMessageInput = () =>
+ wrapper.findByPlaceholderText(SetStatusForm.i18n.statusMessagePlaceholder);
+ const findSelectedEmoji = (emoji) =>
+ wrapper.findByTestId('selected-emoji').find(`gl-emoji[data-name="${emoji}"]`);
+
+ it('sets up emoji autocomplete for the message input', async () => {
+ const gfmAutoCompleteSetupSpy = jest.spyOn(GfmAutoComplete.prototype, 'setup');
+
+ await createComponent();
+
+ expect(gfmAutoCompleteSetupSpy).toHaveBeenCalledWith($(findMessageInput().element), {
+ emojis: true,
+ });
+ });
+
+ describe('when emoji is set', () => {
+ it('displays emoji', async () => {
+ await createComponent();
+
+ expect(findSelectedEmoji(defaultPropsData.emoji).exists()).toBe(true);
+ });
+ });
+
+ describe('when emoji is not set and message is changed', () => {
+ it('displays default emoji', async () => {
+ await createComponent({
+ propsData: {
+ emoji: '',
+ },
+ });
+
+ await findMessageInput().trigger('keyup');
+
+ expect(findSelectedEmoji(defaultPropsData.defaultEmoji).exists()).toBe(true);
+ });
+ });
+
+ describe('when message is set', () => {
+ it('displays filled in message input', async () => {
+ await createComponent();
+
+ expect(findMessageInput().element.value).toBe(defaultPropsData.message);
+ });
+ });
+
+ describe('when clear status after is set', () => {
+ it('displays value in dropdown toggle button', async () => {
+ const clearStatusAfter = timeRanges[0];
+
+ await createComponent({
+ propsData: {
+ clearStatusAfter,
+ },
+ });
+
+ expect(wrapper.findByRole('button', { name: clearStatusAfter.label }).exists()).toBe(true);
+ });
+ });
+
+ describe('when emoji is changed', () => {
+ beforeEach(async () => {
+ await createComponent();
+
+ wrapper.findComponent(EmojiPicker).vm.$emit('click', defaultPropsData.emoji);
+ });
+
+ it('emits `emoji-click` event', () => {
+ expect(wrapper.emitted('emoji-click')).toEqual([[defaultPropsData.emoji]]);
+ });
+ });
+
+ describe('when message is changed', () => {
+ it('emits `message-input` event', async () => {
+ await createComponent();
+
+ const newMessage = 'Foo bar baz';
+
+ await findMessageInput().setValue(newMessage);
+
+ expect(wrapper.emitted('message-input')).toEqual([[newMessage]]);
+ });
+ });
+
+ describe('when availability checkbox is changed', () => {
+ it('emits `availability-input` event', async () => {
+ await createComponent();
+
+ await wrapper
+ .findByLabelText(
+ `${SetStatusForm.i18n.availabilityCheckboxLabel} ${SetStatusForm.i18n.availabilityCheckboxHelpText}`,
+ )
+ .setChecked();
+
+ expect(wrapper.emitted('availability-input')).toEqual([[true]]);
+ });
+ });
+
+ describe('when `Clear status after` dropdown is changed', () => {
+ it('emits `clear-status-after-click`', async () => {
+ await wrapper.findByTestId('thirtyMinutes').trigger('click');
+
+ expect(wrapper.emitted('clear-status-after-click')).toEqual([[timeRanges[0]]]);
+ });
+ });
+
+ describe('when clear status button is clicked', () => {
+ beforeEach(async () => {
+ await createComponent();
+
+ await wrapper
+ .findByRole('button', { name: SetStatusForm.i18n.clearStatusButtonLabel })
+ .trigger('click');
+ });
+
+ it('clears emoji and message', () => {
+ expect(wrapper.emitted('emoji-click')).toEqual([['']]);
+ expect(wrapper.emitted('message-input')).toEqual([['']]);
+ expect(wrapper.findByTestId('no-emoji-placeholder').exists()).toBe(true);
+ });
+ });
+
+ describe('when `currentClearStatusAfter` prop is set', () => {
+ it('displays clear status message', async () => {
+ const date = '2022-08-25 21:14:48 UTC';
+
+ await createComponent({
+ propsData: {
+ currentClearStatusAfter: date,
+ },
+ });
+
+ expect(
+ wrapper.findByText(sprintf(SetStatusForm.i18n.clearStatusAfterMessage, { date })).exists(),
+ ).toBe(true);
+ });
+ });
+});
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 e3b5478290a..c5fb590646d 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,14 +1,14 @@
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
import createFlash from '~/flash';
import stubChildren from 'helpers/stub_children';
-import SetStatusModalWrapper, {
- AVAILABILITY_STATUS,
-} from '~/set_status_modal/set_status_modal_wrapper.vue';
+import SetStatusModalWrapper from '~/set_status_modal/set_status_modal_wrapper.vue';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
+import SetStatusForm from '~/set_status_modal/set_status_form.vue';
jest.mock('~/flash');
@@ -33,7 +33,7 @@ describe('SetStatusModalWrapper', () => {
};
const createComponent = (props = {}) => {
- return mount(SetStatusModalWrapper, {
+ return mountExtended(SetStatusModalWrapper, {
propsData: {
...defaultProps,
...props,
@@ -42,6 +42,7 @@ describe('SetStatusModalWrapper', () => {
...stubChildren(SetStatusModalWrapper),
GlFormInput: false,
GlFormInputGroup: false,
+ SetStatusForm: false,
EmojiPicker: EmojiPickerStub,
},
mocks: {
@@ -51,7 +52,8 @@ describe('SetStatusModalWrapper', () => {
};
const findModal = () => wrapper.find(GlModal);
- const findFormField = (field) => wrapper.find(`[name="user[status][${field}]"]`);
+ const findMessageField = () =>
+ wrapper.findByPlaceholderText(SetStatusForm.i18n.statusMessagePlaceholder);
const findClearStatusButton = () => wrapper.find('.js-clear-user-status-button');
const findAvailabilityCheckbox = () => wrapper.find(GlFormCheckbox);
const findClearStatusAtMessage = () => wrapper.find('[data-testid="clear-status-at-message"]');
@@ -81,14 +83,8 @@ describe('SetStatusModalWrapper', () => {
return initModal();
});
- it('sets the hidden status emoji field', () => {
- const field = findFormField('emoji');
- expect(field.exists()).toBe(true);
- expect(field.element.value).toBe(defaultEmoji);
- });
-
it('sets the message field', () => {
- const field = findFormField('message');
+ const field = findMessageField();
expect(field.exists()).toBe(true);
expect(field.element.value).toBe(defaultMessage);
});
@@ -118,10 +114,10 @@ describe('SetStatusModalWrapper', () => {
});
});
- it('sets emojiTag when clicking in emoji picker', async () => {
+ it('passes emoji to `SetStatusForm`', async () => {
await getEmojiPicker().vm.$emit('click', 'thumbsup');
- expect(wrapper.vm.emojiTag).toContain('data-name="thumbsup"');
+ expect(wrapper.findComponent(SetStatusForm).props('emoji')).toBe('thumbsup');
});
});
@@ -133,7 +129,7 @@ describe('SetStatusModalWrapper', () => {
});
it('does not set the message field', () => {
- expect(findFormField('message').element.value).toBe('');
+ expect(findMessageField().element.value).toBe('');
});
it('hides the clear status button', () => {
@@ -141,18 +137,6 @@ describe('SetStatusModalWrapper', () => {
});
});
- describe('with no currentEmoji set', () => {
- beforeEach(async () => {
- await initEmojiMock();
- wrapper = createComponent({ currentEmoji: '' });
- return initModal();
- });
-
- it('does not set the hidden status emoji field', () => {
- expect(findFormField('emoji').element.value).toBe('');
- });
- });
-
describe('with currentClearStatusAfter set', () => {
beforeEach(async () => {
await initEmojiMock();
@@ -182,8 +166,7 @@ describe('SetStatusModalWrapper', () => {
findModal().vm.$emit('secondary');
await nextTick();
- expect(findFormField('message').element.value).toBe('');
- expect(findFormField('emoji').element.value).toBe('');
+ expect(findMessageField().element.value).toBe('');
});
it('clicking "setStatus" submits the user status', async () => {
@@ -194,7 +177,7 @@ describe('SetStatusModalWrapper', () => {
findAvailabilityCheckbox().vm.$emit('input', true);
// set the currentClearStatusAfter to 30 minutes
- wrapper.find('[data-testid="thirtyMinutes"]').vm.$emit('click');
+ wrapper.find('[data-testid="thirtyMinutes"]').trigger('click');
findModal().vm.$emit('primary');
await nextTick();
diff --git a/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js b/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js
new file mode 100644
index 00000000000..eaee0e77311
--- /dev/null
+++ b/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js
@@ -0,0 +1,156 @@
+import { nextTick } from 'vue';
+import { cloneDeep } from 'lodash';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { resetHTMLFixture } from 'helpers/fixtures';
+import { useFakeDate } from 'helpers/fake_date';
+import UserProfileSetStatusWrapper from '~/set_status_modal/user_profile_set_status_wrapper.vue';
+import SetStatusForm from '~/set_status_modal/set_status_form.vue';
+import { TIME_RANGES_WITH_NEVER, NEVER_TIME_RANGE } from '~/set_status_modal/constants';
+
+describe('UserProfileSetStatusWrapper', () => {
+ let wrapper;
+
+ const defaultProvide = {
+ fields: {
+ emoji: { name: 'user[status][emoji]', id: 'user_status_emoji', value: '8ball' },
+ message: { name: 'user[status][message]', id: 'user_status_message', value: 'foo bar' },
+ availability: {
+ name: 'user[status][availability]',
+ id: 'user_status_availability',
+ value: 'busy',
+ },
+ clearStatusAfter: {
+ name: 'user[status][clear_status_after]',
+ id: 'user_status_clear_status_after',
+ value: '2022-09-03 03:06:26 UTC',
+ },
+ },
+ };
+
+ const createComponent = ({ provide = {} } = {}) => {
+ wrapper = mountExtended(UserProfileSetStatusWrapper, {
+ provide: {
+ ...defaultProvide,
+ ...provide,
+ },
+ });
+ };
+
+ const findInput = (name) => wrapper.find(`[name="${name}"]`);
+ const findSetStatusForm = () => wrapper.findComponent(SetStatusForm);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders `SetStatusForm` component and passes expected props', () => {
+ createComponent();
+
+ expect(cloneDeep(findSetStatusForm().props())).toMatchObject({
+ defaultEmoji: 'speech_balloon',
+ emoji: defaultProvide.fields.emoji.value,
+ message: defaultProvide.fields.message.value,
+ availability: true,
+ clearStatusAfter: NEVER_TIME_RANGE,
+ currentClearStatusAfter: defaultProvide.fields.clearStatusAfter.value,
+ });
+ });
+
+ it.each`
+ input
+ ${'emoji'}
+ ${'message'}
+ ${'availability'}
+ `('renders hidden $input input with value set', ({ input }) => {
+ createComponent();
+
+ expect(findInput(defaultProvide.fields[input].name).attributes('value')).toBe(
+ defaultProvide.fields[input].value,
+ );
+ });
+
+ describe('when clear status after dropdown is set to `Never`', () => {
+ it('renders hidden clear status after input with value unset', () => {
+ createComponent();
+
+ expect(
+ findInput(defaultProvide.fields.clearStatusAfter.name).attributes('value'),
+ ).toBeUndefined();
+ });
+ });
+
+ describe('when clear status after dropdown has a value selected', () => {
+ it('renders hidden clear status after input with value set', async () => {
+ createComponent();
+
+ findSetStatusForm().vm.$emit('clear-status-after-click', TIME_RANGES_WITH_NEVER[1]);
+
+ await nextTick();
+
+ expect(findInput(defaultProvide.fields.clearStatusAfter.name).attributes('value')).toBe(
+ TIME_RANGES_WITH_NEVER[1].shortcut,
+ );
+ });
+ });
+
+ describe('when emoji is changed', () => {
+ it('updates hidden emoji input value', async () => {
+ createComponent();
+
+ const newEmoji = 'basketball';
+
+ findSetStatusForm().vm.$emit('emoji-click', newEmoji);
+
+ await nextTick();
+
+ expect(findInput(defaultProvide.fields.emoji.name).attributes('value')).toBe(newEmoji);
+ });
+ });
+
+ describe('when message is changed', () => {
+ it('updates hidden message input value', async () => {
+ createComponent();
+
+ const newMessage = 'foo bar baz';
+
+ findSetStatusForm().vm.$emit('message-input', newMessage);
+
+ await nextTick();
+
+ expect(findInput(defaultProvide.fields.message.name).attributes('value')).toBe(newMessage);
+ });
+ });
+
+ describe('when form is successfully submitted', () => {
+ // 2022-09-02 00:00:00 UTC
+ useFakeDate(2022, 8, 2);
+
+ const form = document.createElement('form');
+ form.classList.add('js-edit-user');
+
+ beforeEach(async () => {
+ document.body.appendChild(form);
+ createComponent();
+
+ const oneDay = TIME_RANGES_WITH_NEVER[4];
+
+ findSetStatusForm().vm.$emit('clear-status-after-click', oneDay);
+
+ await nextTick();
+
+ form.dispatchEvent(new Event('ajax:success'));
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ it('updates clear status after dropdown to `Never`', () => {
+ expect(findSetStatusForm().props('clearStatusAfter')).toBe(NEVER_TIME_RANGE);
+ });
+
+ it('updates `currentClearStatusAfter` prop', () => {
+ expect(findSetStatusForm().props('currentClearStatusAfter')).toBe('2022-09-03 00:00:00 UTC');
+ });
+ });
+});
diff --git a/spec/frontend/set_status_modal/utils_spec.js b/spec/frontend/set_status_modal/utils_spec.js
index 273f30f8311..1e918b75a98 100644
--- a/spec/frontend/set_status_modal/utils_spec.js
+++ b/spec/frontend/set_status_modal/utils_spec.js
@@ -1,4 +1,5 @@
-import { AVAILABILITY_STATUS, isUserBusy } from '~/set_status_modal/utils';
+import { isUserBusy } from '~/set_status_modal/utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
describe('Set status modal utils', () => {
describe('isUserBusy', () => {
diff --git a/spec/frontend/sidebar/assignee_title_spec.js b/spec/frontend/sidebar/assignee_title_spec.js
index 3079cb28406..e29e3d489a5 100644
--- a/spec/frontend/sidebar/assignee_title_spec.js
+++ b/spec/frontend/sidebar/assignee_title_spec.js
@@ -85,7 +85,7 @@ describe('AssigneeTitle component', () => {
editable: false,
});
- expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
it('renders spinner when loading', () => {
@@ -95,7 +95,7 @@ describe('AssigneeTitle component', () => {
editable: false,
});
- expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('does not render edit link when not editable', () => {
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 517b4f12559..8cde70ff8da 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
@@ -143,7 +143,7 @@ describe('AssigneeAvatarLink component', () => {
issuableType | userId
${'merge_request'} | ${undefined}
${'issue'} | ${'1'}
- `('it sets data-user-id as $userId for $issuableType', ({ issuableType, userId }) => {
+ `('sets data-user-id as $userId for $issuableType', ({ issuableType, userId }) => {
createComponent({
issuableType,
});
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 5aa8264b98c..81ff51133bf 100644
--- a/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
@@ -23,7 +23,7 @@ describe('CollapsedAssigneeList component', () => {
const findNoUsersIcon = () => wrapper.find(GlIcon);
const findAvatarCounter = () => wrapper.find('.avatar-counter');
- const findAssignees = () => wrapper.findAll(CollapsedAssignee);
+ const findAssignees = () => wrapper.findAllComponents(CollapsedAssignee);
const getTooltipTitle = () => wrapper.attributes('title');
afterEach(() => {
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
index 88015ed42a3..3644a51c7fd 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
@@ -65,6 +65,7 @@ describe('Sidebar assignees widget', () => {
issuableId: 0,
fullPath: '/mygroup/myProject',
allowMultipleAssignees: true,
+ editable: true,
...props,
},
provide: {
@@ -350,6 +351,17 @@ describe('Sidebar assignees widget', () => {
});
});
+ describe('when issuable is not editable by the user', () => {
+ beforeEach(async () => {
+ createComponent({ props: { editable: false } });
+ await waitForPromises();
+ });
+
+ it('passes editable prop as false to IssuableAssignees', () => {
+ expect(findAssignees().props('editable')).toBe(false);
+ });
+ });
+
it('includes the real-time assignees component', async () => {
createComponent();
await waitForPromises();
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 f7437386814..b902d7313fd 100644
--- a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -42,7 +42,7 @@ describe('UncollapsedAssigneeList component', () => {
});
it('only has one user', () => {
- expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(1);
+ expect(wrapper.findAllComponents(AssigneeAvatarLink).length).toBe(1);
});
it('calls the AssigneeAvatarLink with the proper props', () => {
@@ -79,7 +79,7 @@ describe('UncollapsedAssigneeList component', () => {
});
it('shows truncated users', () => {
- expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT);
+ expect(wrapper.findAllComponents(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT);
});
describe('when more button is clicked', () => {
@@ -94,7 +94,9 @@ describe('UncollapsedAssigneeList component', () => {
});
it('shows all users', () => {
- expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT + 1);
+ expect(wrapper.findAllComponents(AssigneeAvatarLink).length).toBe(
+ DEFAULT_RENDER_COUNT + 1,
+ );
});
});
});
diff --git a/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
index 4dbf3d426bb..37c16bc9235 100644
--- a/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
+++ b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
const name = 'Administrator';
diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js
index 7775ed6aa37..1ea035c7184 100644
--- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js
+++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js
@@ -71,12 +71,7 @@ describe('Sidebar Confidentiality Form', () => {
it('creates a flash if mutation contains errors', async () => {
createComponent({
mutate: jest.fn().mockResolvedValue({
- data: {
- issuableSetConfidential: {
- issuable: { confidential: false },
- errors: ['Houston, we have a problem!'],
- },
- },
+ data: { issuableSetConfidential: { errors: ['Houston, we have a problem!'] } },
}),
});
findConfidentialToggle().vm.$emit('click', new MouseEvent('click'));
@@ -87,24 +82,6 @@ describe('Sidebar Confidentiality Form', () => {
});
});
- it('emits `closeForm` event with confidentiality value when mutation is successful', async () => {
- createComponent({
- mutate: jest.fn().mockResolvedValue({
- data: {
- issuableSetConfidential: {
- issuable: { confidential: true },
- errors: [],
- },
- },
- }),
- });
-
- findConfidentialToggle().vm.$emit('click', new MouseEvent('click'));
- await waitForPromises();
-
- expect(wrapper.emitted('closeForm')).toEqual([[{ confidential: true }]]);
- });
-
describe('when issue is not confidential', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
index 18ee423d12e..3a3f0b1d9fa 100644
--- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
+++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
@@ -132,7 +132,6 @@ describe('Sidebar Confidentiality Widget', () => {
it('closes the form and dispatches an event when `closeForm` is emitted', async () => {
createComponent();
const el = wrapper.vm.$el;
- const closeFormPayload = { confidential: true };
jest.spyOn(el, 'dispatchEvent');
await waitForPromises();
@@ -141,12 +140,12 @@ describe('Sidebar Confidentiality Widget', () => {
expect(findConfidentialityForm().isVisible()).toBe(true);
- findConfidentialityForm().vm.$emit('closeForm', closeFormPayload);
+ findConfidentialityForm().vm.$emit('closeForm');
await nextTick();
expect(findConfidentialityForm().isVisible()).toBe(false);
expect(el.dispatchEvent).toHaveBeenCalled();
- expect(wrapper.emitted('closeForm')).toEqual([[closeFormPayload]]);
+ expect(wrapper.emitted('closeForm')).toEqual([[]]);
});
it('emits `expandSidebar` event when it is emitted from child component', async () => {
diff --git a/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js b/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js
index fda21e06987..a7556b9110c 100644
--- a/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js
+++ b/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js
@@ -5,10 +5,10 @@ import SidebarInheritDate from '~/sidebar/components/date/sidebar_inherit_date.v
describe('SidebarInheritDate', () => {
let wrapper;
- const findFixedFormattedDate = () => wrapper.findAll(SidebarFormattedDate).at(0);
- const findInheritFormattedDate = () => wrapper.findAll(SidebarFormattedDate).at(1);
- const findFixedRadio = () => wrapper.findAll(GlFormRadio).at(0);
- const findInheritRadio = () => wrapper.findAll(GlFormRadio).at(1);
+ const findFixedFormattedDate = () => wrapper.findAllComponents(SidebarFormattedDate).at(0);
+ const findInheritFormattedDate = () => wrapper.findAllComponents(SidebarFormattedDate).at(1);
+ const findFixedRadio = () => wrapper.findAllComponents(GlFormRadio).at(0);
+ const findInheritRadio = () => wrapper.findAllComponents(GlFormRadio).at(1);
const createComponent = ({ dueDateIsFixed = false } = {}) => {
wrapper = shallowMount(SidebarInheritDate, {
@@ -36,8 +36,8 @@ describe('SidebarInheritDate', () => {
});
it('displays formatted fixed and inherited dates with radio buttons', () => {
- expect(wrapper.findAll(SidebarFormattedDate)).toHaveLength(2);
- expect(wrapper.findAll(GlFormRadio)).toHaveLength(2);
+ expect(wrapper.findAllComponents(SidebarFormattedDate)).toHaveLength(2);
+ expect(wrapper.findAllComponents(GlFormRadio)).toHaveLength(2);
expect(findFixedFormattedDate().props('formattedDate')).toBe('Apr 15, 2021');
expect(findInheritFormattedDate().props('formattedDate')).toBe('May 15, 2021');
expect(findFixedRadio().text()).toBe('Fixed:');
diff --git a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
index 2c24df2436a..d00c8dcb653 100644
--- a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
@@ -52,7 +52,7 @@ describe('UncollapsedReviewerList component', () => {
});
it('only has one user', () => {
- expect(wrapper.findAll(ReviewerAvatarLink).length).toBe(1);
+ expect(wrapper.findAllComponents(ReviewerAvatarLink).length).toBe(1);
});
it('shows one user with avatar, and author name', () => {
@@ -96,7 +96,7 @@ describe('UncollapsedReviewerList component', () => {
});
it('has both users', () => {
- expect(wrapper.findAll(ReviewerAvatarLink).length).toBe(2);
+ expect(wrapper.findAllComponents(ReviewerAvatarLink).length).toBe(2);
});
it('shows both users with avatar, and author name', () => {
diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
index 5d80a221d8e..83eb9a18597 100644
--- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
+++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
@@ -97,7 +97,7 @@ describe('SidebarSeverity', () => {
});
});
- it('shows error alert when severity update fails ', async () => {
+ it('shows error alert when severity update fails', async () => {
const errorMsg = 'Something went wrong';
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValueOnce(errorMsg);
findCriticalSeverityDropdownItem().vm.$emit('click');
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
index 8ebd2dabfc2..6761731c093 100644
--- a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
@@ -238,6 +238,24 @@ describe('SidebarDropdownWidget', () => {
expect(findSelectedAttribute().text()).toBe('None');
});
});
+
+ describe("when user doesn't have permission to view current attribute", () => {
+ it('renders no permission text', () => {
+ createComponent({
+ data: {
+ hasCurrentAttribute: true,
+ currentAttribute: null,
+ },
+ queries: {
+ currentAttribute: { loading: false },
+ },
+ });
+
+ expect(findSelectedAttribute().text()).toBe(
+ `You don't have permission to view this ${wrapper.props('issuableAttribute')}.`,
+ );
+ });
+ });
});
describe('when a user can edit', () => {
diff --git a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
index 9a68940590d..430acf9f9e7 100644
--- a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
+++ b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
@@ -156,7 +156,7 @@ describe('Sidebar Subscriptions Widget', () => {
});
await waitForPromises();
- await wrapper.find('.dropdown-item').trigger('click');
+ await wrapper.find('[data-testid="notifications-toggle"]').vm.$emit('change');
await waitForPromises();
diff --git a/spec/frontend/sidebar/components/time_tracking/report_spec.js b/spec/frontend/sidebar/components/time_tracking/report_spec.js
index 5ed8810e95e..4e619a4e609 100644
--- a/spec/frontend/sidebar/components/time_tracking/report_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/report_spec.js
@@ -161,7 +161,6 @@ describe('Issuable Time Tracking Report', () => {
id: timelogToRemoveId,
},
},
- update: expect.anything(),
});
});
@@ -179,7 +178,6 @@ describe('Issuable Time Tracking Report', () => {
id: timelogToRemoveId,
},
},
- update: expect.anything(),
});
expect(createFlash).toHaveBeenCalledWith({
diff --git a/spec/frontend/sidebar/issuable_assignees_spec.js b/spec/frontend/sidebar/issuable_assignees_spec.js
index 3563d478f3f..dc59b68bbd4 100644
--- a/spec/frontend/sidebar/issuable_assignees_spec.js
+++ b/spec/frontend/sidebar/issuable_assignees_spec.js
@@ -12,6 +12,7 @@ describe('IssuableAssignees', () => {
},
propsData: {
users: [],
+ editable: true,
...props,
},
});
@@ -25,15 +26,19 @@ describe('IssuableAssignees', () => {
});
describe('when no assignees are present', () => {
- it('renders "None - assign yourself" when user is logged in', () => {
- createComponent({ signedIn: true });
- expect(findEmptyAssignee().text()).toBe('None - assign yourself');
- });
-
- it('renders "None" when user is not logged in', () => {
- createComponent();
- expect(findEmptyAssignee().text()).toBe('None');
- });
+ it.each`
+ signedIn | editable | message
+ ${true} | ${true} | ${'None - assign yourself'}
+ ${true} | ${false} | ${'None'}
+ ${false} | ${true} | ${'None'}
+ ${false} | ${false} | ${'None'}
+ `(
+ 'renders "$message" when signedIn is $signedIn and editable is $editable',
+ ({ signedIn, editable, message }) => {
+ createComponent({ signedIn, editable });
+ expect(findEmptyAssignee().text()).toBe(message);
+ },
+ );
});
describe('when assignees are present', () => {
diff --git a/spec/frontend/sidebar/lock/issuable_lock_form_spec.js b/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
index bb757fdf63b..986ccaea4b6 100644
--- a/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
+++ b/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
@@ -130,7 +130,7 @@ describe('IssuableLockForm', () => {
expect(findEditForm().exists()).toBe(true);
});
- it('tracks the event ', () => {
+ it('tracks the event', () => {
const spy = mockTracking('_category_', wrapper.element, jest.spyOn);
triggerEvent(findEditLink().element);
diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js
index 9c6e23e928c..2afe9647cbe 100644
--- a/spec/frontend/sidebar/mock_data.js
+++ b/spec/frontend/sidebar/mock_data.js
@@ -497,6 +497,11 @@ export const searchResponse = {
user: mockUser2,
},
],
+ pageInfo: {
+ hasNextPage: false,
+ endCursor: null,
+ startCursor: null,
+ },
},
},
},
@@ -559,6 +564,11 @@ export const projectMembersResponse = {
},
},
],
+ pageInfo: {
+ hasNextPage: false,
+ startCursor: null,
+ endCursor: null,
+ },
},
},
},
diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js
index e32694abcce..355f0c45bbe 100644
--- a/spec/frontend/sidebar/sidebar_mediator_spec.js
+++ b/spec/frontend/sidebar/sidebar_mediator_spec.js
@@ -27,7 +27,7 @@ describe('Sidebar mediator', () => {
mock.restore();
});
- it('assigns yourself ', () => {
+ it('assigns yourself', () => {
mediator.assignYourself();
expect(mediator.store.currentUser).toEqual(mediatorMockData.currentUser);
diff --git a/spec/frontend/sidebar/sidebar_move_issue_spec.js b/spec/frontend/sidebar/sidebar_move_issue_spec.js
index 7bb7b18adf8..2e6807ed9d8 100644
--- a/spec/frontend/sidebar/sidebar_move_issue_spec.js
+++ b/spec/frontend/sidebar/sidebar_move_issue_spec.js
@@ -7,6 +7,7 @@ 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 { GitLabDropdown } from '~/deprecated_jquery_dropdown/gl_dropdown';
import Mock from './mock_data';
jest.mock('~/flash');
@@ -75,7 +76,9 @@ describe('SidebarMoveIssue', () => {
it('should initialize the deprecatedJQueryDropdown', () => {
test.sidebarMoveIssue.initDropdown();
- expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeTruthy();
+ expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeInstanceOf(
+ GitLabDropdown,
+ );
});
it('escapes html from project name', async () => {
@@ -97,7 +100,7 @@ describe('SidebarMoveIssue', () => {
test.sidebarMoveIssue.onConfirmClicked();
expect(test.mediator.moveIssue).toHaveBeenCalled();
- expect(test.$confirmButton.prop('disabled')).toBeTruthy();
+ expect(test.$confirmButton.prop('disabled')).toBe(true);
expect(test.$confirmButton.hasClass('is-loading')).toBe(true);
});
@@ -113,7 +116,7 @@ describe('SidebarMoveIssue', () => {
await waitForPromises();
expect(createFlash).toHaveBeenCalled();
- expect(test.$confirmButton.prop('disabled')).toBeFalsy();
+ expect(test.$confirmButton.prop('disabled')).toBe(false);
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
});
@@ -139,7 +142,7 @@ describe('SidebarMoveIssue', () => {
test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
- expect(test.$confirmButton.prop('disabled')).toBeTruthy();
+ expect(test.$confirmButton.prop('disabled')).toBe(true);
});
it('should set moveToProjectId on dropdown item click', async () => {
diff --git a/spec/frontend/sidebar/todo_spec.js b/spec/frontend/sidebar/todo_spec.js
index 9316268d2ad..5f696b237e0 100644
--- a/spec/frontend/sidebar/todo_spec.js
+++ b/spec/frontend/sidebar/todo_spec.js
@@ -55,7 +55,7 @@ describe('SidebarTodo', () => {
wrapper.find('button').trigger('click');
await nextTick();
- expect(wrapper.emitted().toggleTodo).toBeTruthy();
+ expect(wrapper.emitted().toggleTodo).toHaveLength(1);
});
it('renders component container element with proper data attributes', () => {
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index f49ceb2fede..cf897414ccb 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -16,10 +16,10 @@ import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_e
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
import {
- SNIPPET_VISIBILITY_PRIVATE,
- SNIPPET_VISIBILITY_INTERNAL,
- SNIPPET_VISIBILITY_PUBLIC,
-} from '~/snippets/constants';
+ VISIBILITY_LEVEL_PRIVATE_STRING,
+ VISIBILITY_LEVEL_INTERNAL_STRING,
+ VISIBILITY_LEVEL_PUBLIC_STRING,
+} from '~/visibility_level/constants';
import CreateSnippetMutation from '~/snippets/mutations/create_snippet.mutation.graphql';
import UpdateSnippetMutation from '~/snippets/mutations/update_snippet.mutation.graphql';
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
@@ -41,7 +41,7 @@ const TEST_SNIPPET_GID = 'gid://gitlab/PersonalSnippet/42';
const createSnippet = () =>
merge(createGQLSnippet(), {
webUrl: TEST_WEB_URL,
- visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
+ visibilityLevel: VISIBILITY_LEVEL_PRIVATE_STRING,
});
const createQueryResponse = (obj = {}) =>
@@ -70,7 +70,7 @@ const getApiData = ({
id,
title = '',
description = '',
- visibilityLevel = SNIPPET_VISIBILITY_PRIVATE,
+ visibilityLevel = VISIBILITY_LEVEL_PRIVATE_STRING,
} = {}) => ({
id,
title,
@@ -128,7 +128,10 @@ describe('Snippet Edit app', () => {
const setDescription = (val) =>
wrapper.findComponent(SnippetDescriptionEdit).vm.$emit('input', val);
- const createComponent = ({ props = {}, selectedLevel = SNIPPET_VISIBILITY_PRIVATE } = {}) => {
+ const createComponent = ({
+ props = {},
+ selectedLevel = VISIBILITY_LEVEL_PRIVATE_STRING,
+ } = {}) => {
if (wrapper) {
throw new Error('wrapper already created');
}
@@ -260,17 +263,18 @@ describe('Snippet Edit app', () => {
},
);
- it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])(
- 'marks %s visibility by default',
- async (visibility) => {
- createComponent({
- props: { snippetGid: '' },
- selectedLevel: visibility,
- });
+ it.each([
+ VISIBILITY_LEVEL_PRIVATE_STRING,
+ VISIBILITY_LEVEL_INTERNAL_STRING,
+ VISIBILITY_LEVEL_PUBLIC_STRING,
+ ])('marks %s visibility by default', async (visibility) => {
+ createComponent({
+ props: { snippetGid: '' },
+ selectedLevel: visibility,
+ });
- expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility);
- },
- );
+ expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility);
+ });
describe('form submission handling', () => {
describe('when creating a new snippet', () => {
diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js
index b29ed97099f..032dcf8e5f5 100644
--- a/spec/frontend/snippets/components/show_spec.js
+++ b/spec/frontend/snippets/components/show_spec.js
@@ -7,10 +7,10 @@ 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 {
- SNIPPET_VISIBILITY_INTERNAL,
- SNIPPET_VISIBILITY_PRIVATE,
- SNIPPET_VISIBILITY_PUBLIC,
-} from '~/snippets/constants';
+ VISIBILITY_LEVEL_INTERNAL_STRING,
+ VISIBILITY_LEVEL_PRIVATE_STRING,
+ VISIBILITY_LEVEL_PUBLIC_STRING,
+} from '~/visibility_level/constants';
import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
import { stubPerformanceWebAPI } from 'helpers/performance';
@@ -69,7 +69,7 @@ describe('Snippet view app', () => {
createComponent({
data: {
snippet: {
- visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
+ visibilityLevel: VISIBILITY_LEVEL_PUBLIC_STRING,
webUrl: 'http://foo.bar',
},
},
@@ -85,7 +85,7 @@ describe('Snippet view app', () => {
},
},
});
- const blobs = wrapper.findAll(SnippetBlob);
+ const blobs = wrapper.findAllComponents(SnippetBlob);
expect(blobs.length).toBe(2);
expect(blobs.at(0).props('blob')).toEqual(Blob);
expect(blobs.at(1).props('blob')).toEqual(BinaryBlob);
@@ -93,11 +93,11 @@ describe('Snippet view app', () => {
describe('Embed dropdown rendering', () => {
it.each`
- visibilityLevel | condition | isRendered
- ${SNIPPET_VISIBILITY_INTERNAL} | ${'not render'} | ${false}
- ${SNIPPET_VISIBILITY_PRIVATE} | ${'not render'} | ${false}
- ${'foo'} | ${'not render'} | ${false}
- ${SNIPPET_VISIBILITY_PUBLIC} | ${'render'} | ${true}
+ visibilityLevel | condition | isRendered
+ ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${'not render'} | ${false}
+ ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${'not render'} | ${false}
+ ${'foo'} | ${'not render'} | ${false}
+ ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${'render'} | ${true}
`('does $condition embed-dropdown by default', ({ visibilityLevel, isRendered }) => {
createComponent({
data: {
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 df98312b498..a650353093d 100644
--- a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
@@ -32,7 +32,7 @@ describe('snippets/components/snippet_blob_actions_edit', () => {
};
const findLabel = () => wrapper.findComponent(GlFormGroup);
- const findBlobEdits = () => wrapper.findAll(SnippetBlobEdit);
+ const findBlobEdits = () => wrapper.findAllComponents(SnippetBlobEdit);
const findBlobsData = () =>
findBlobEdits().wrappers.map((x) => ({
blob: x.props('blob'),
diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js
index c395112e313..aa31377f390 100644
--- a/spec/frontend/snippets/components/snippet_blob_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js
@@ -15,7 +15,7 @@ import {
BLOB_RENDER_ERRORS,
} from '~/blob/components/constants';
import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue';
-import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
+import { VISIBILITY_LEVEL_PUBLIC_STRING } from '~/visibility_level/constants';
import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
describe('Blob Embeddable', () => {
@@ -23,7 +23,7 @@ describe('Blob Embeddable', () => {
const snippet = {
id: 'gid://foo.bar/snippet',
webUrl: 'https://foo.bar',
- visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
+ visibilityLevel: VISIBILITY_LEVEL_PUBLIC_STRING,
};
const dataMock = {
activeViewerType: SimpleViewerMock.type,
diff --git a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
index 62d1ac9b476..2d043a5caba 100644
--- a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
@@ -2,10 +2,12 @@ import { GlFormRadio, GlIcon, GlFormRadioGroup, GlLink } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
import {
+ VISIBILITY_LEVEL_PRIVATE_STRING,
+ VISIBILITY_LEVEL_INTERNAL_STRING,
+ VISIBILITY_LEVEL_PUBLIC_STRING,
+} from '~/visibility_level/constants';
+import {
SNIPPET_VISIBILITY,
- SNIPPET_VISIBILITY_PRIVATE,
- SNIPPET_VISIBILITY_INTERNAL,
- SNIPPET_VISIBILITY_PUBLIC,
SNIPPET_LEVELS_RESTRICTED,
SNIPPET_LEVELS_DISABLED,
} from '~/snippets/constants';
@@ -38,7 +40,7 @@ describe('Snippet Visibility Edit component', () => {
}
const findLink = () => wrapper.find('label').find(GlLink);
- const findRadios = () => wrapper.find(GlFormRadioGroup).findAll(GlFormRadio);
+ const findRadios = () => wrapper.find(GlFormRadioGroup).findAllComponents(GlFormRadio);
const findRadiosData = () =>
findRadios().wrappers.map((x) => {
return {
@@ -75,19 +77,19 @@ describe('Snippet Visibility Edit component', () => {
const findRestrictedInfo = () => wrapper.find('[data-testid="restricted-levels-info"]');
const RESULTING_OPTIONS = {
0: {
- value: SNIPPET_VISIBILITY_PRIVATE,
+ value: VISIBILITY_LEVEL_PRIVATE_STRING,
icon: SNIPPET_VISIBILITY.private.icon,
text: SNIPPET_VISIBILITY.private.label,
description: SNIPPET_VISIBILITY.private.description,
},
10: {
- value: SNIPPET_VISIBILITY_INTERNAL,
+ value: VISIBILITY_LEVEL_INTERNAL_STRING,
icon: SNIPPET_VISIBILITY.internal.icon,
text: SNIPPET_VISIBILITY.internal.label,
description: SNIPPET_VISIBILITY.internal.description,
},
20: {
- value: SNIPPET_VISIBILITY_PUBLIC,
+ value: VISIBILITY_LEVEL_PUBLIC_STRING,
icon: SNIPPET_VISIBILITY.public.icon,
text: SNIPPET_VISIBILITY.public.label,
description: SNIPPET_VISIBILITY.public.description,
@@ -130,7 +132,7 @@ describe('Snippet Visibility Edit component', () => {
createComponent({ propsData: { isProjectSnippet: true }, deep: true });
expect(findRadiosData()[0]).toEqual({
- value: SNIPPET_VISIBILITY_PRIVATE,
+ value: VISIBILITY_LEVEL_PRIVATE_STRING,
icon: SNIPPET_VISIBILITY.private.icon,
text: SNIPPET_VISIBILITY.private.label,
description: SNIPPET_VISIBILITY.private.description_project,
@@ -141,7 +143,7 @@ describe('Snippet Visibility Edit component', () => {
describe('functionality', () => {
it('pre-selects correct option in the list', () => {
- const value = SNIPPET_VISIBILITY_INTERNAL;
+ const value = VISIBILITY_LEVEL_INTERNAL_STRING;
createComponent({ propsData: { value } });
diff --git a/spec/frontend/surveys/merge_request_performance/app_spec.js b/spec/frontend/surveys/merge_request_performance/app_spec.js
index cd549155914..af91d8aeb6b 100644
--- a/spec/frontend/surveys/merge_request_performance/app_spec.js
+++ b/spec/frontend/surveys/merge_request_performance/app_spec.js
@@ -6,6 +6,17 @@ import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisse
import MergeRequestExperienceSurveyApp from '~/surveys/merge_request_experience/app.vue';
import SatisfactionRate from '~/surveys/components/satisfaction_rate.vue';
+const createRenderTrackedArguments = () => [
+ undefined,
+ 'survey:mr_experience',
+ {
+ label: 'render',
+ extra: {
+ accountAge: 0,
+ },
+ },
+];
+
describe('MergeRequestExperienceSurveyApp', () => {
let trackingSpy;
let wrapper;
@@ -24,6 +35,7 @@ describe('MergeRequestExperienceSurveyApp', () => {
dismiss,
shouldShowCallout,
});
+ trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
wrapper = shallowMountExtended(MergeRequestExperienceSurveyApp, {
propsData: {
accountAge: 0,
@@ -35,10 +47,13 @@ describe('MergeRequestExperienceSurveyApp', () => {
});
};
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
describe('when user callout is visible', () => {
beforeEach(() => {
createWrapper();
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
it('shows survey', async () => {
@@ -47,14 +62,46 @@ describe('MergeRequestExperienceSurveyApp', () => {
expect(wrapper.emitted().close).toBe(undefined);
});
- it('triggers user callout on close', async () => {
- findCloseButton().vm.$emit('click');
- expect(dismiss).toHaveBeenCalledTimes(1);
+ it('tracks render once', async () => {
+ expect(trackingSpy).toHaveBeenCalledWith(...createRenderTrackedArguments());
});
- it('emits close event on close button click', async () => {
- findCloseButton().vm.$emit('click');
- expect(wrapper.emitted()).toMatchObject({ close: [[]] });
+ it("doesn't track subsequent renders", async () => {
+ createWrapper();
+ expect(trackingSpy).toHaveBeenCalledWith(...createRenderTrackedArguments());
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ });
+
+ describe('when close button clicked', () => {
+ beforeEach(() => {
+ findCloseButton().vm.$emit('click');
+ });
+
+ it('triggers user callout on close', async () => {
+ expect(dismiss).toHaveBeenCalledTimes(1);
+ });
+
+ it('emits close event on close button click', async () => {
+ expect(wrapper.emitted()).toMatchObject({ close: [[]] });
+ });
+
+ it('tracks dismissal', async () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'survey:mr_experience', {
+ label: 'dismiss',
+ extra: {
+ accountAge: 0,
+ },
+ });
+ });
+
+ it('tracks subsequent renders', async () => {
+ createWrapper();
+ expect(trackingSpy.mock.calls).toEqual([
+ createRenderTrackedArguments(),
+ expect.anything(),
+ createRenderTrackedArguments(),
+ ]);
+ });
});
it('applies correct feature name for user callout', () => {
@@ -135,6 +182,10 @@ describe('MergeRequestExperienceSurveyApp', () => {
it('emits close event', async () => {
expect(wrapper.emitted()).toMatchObject({ close: [[]] });
});
+
+ it("doesn't track anything", async () => {
+ expect(trackingSpy).toHaveBeenCalledTimes(0);
+ });
});
describe('when Escape key is pressed', () => {
@@ -148,5 +199,14 @@ describe('MergeRequestExperienceSurveyApp', () => {
expect(wrapper.emitted()).toMatchObject({ close: [[]] });
expect(dismiss).toHaveBeenCalledTimes(1);
});
+
+ it('tracks dismissal', async () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'survey:mr_experience', {
+ label: 'dismiss',
+ extra: {
+ accountAge: 0,
+ },
+ });
+ });
});
});
diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js
index 16ffd2b7013..12a44452717 100644
--- a/spec/frontend/terraform/components/states_table_spec.js
+++ b/spec/frontend/terraform/components/states_table_spec.js
@@ -134,7 +134,7 @@ describe('StatesTable', () => {
await nextTick();
};
- const findActions = () => wrapper.findAll(StateActions);
+ const findActions = () => wrapper.findAllComponents(StateActions);
beforeEach(() => {
return createComponent();
diff --git a/spec/frontend/token_access/mock_data.js b/spec/frontend/token_access/mock_data.js
index 0f121fd1beb..6e2908e659f 100644
--- a/spec/frontend/token_access/mock_data.js
+++ b/spec/frontend/token_access/mock_data.js
@@ -24,19 +24,6 @@ export const disabledJobTokenScope = {
},
};
-export const updateJobTokenScope = {
- data: {
- ciCdSettingsUpdate: {
- ciCdSettings: {
- jobTokenScopeEnabled: true,
- __typename: 'ProjectCiCdSetting',
- },
- errors: [],
- __typename: 'CiCdSettingsUpdatePayload',
- },
- },
-};
-
export const projectsWithScope = {
data: {
project: {
diff --git a/spec/frontend/token_access/token_access_spec.js b/spec/frontend/token_access/token_access_spec.js
index 5aaeebd5af4..024e7dfff8c 100644
--- a/spec/frontend/token_access/token_access_spec.js
+++ b/spec/frontend/token_access/token_access_spec.js
@@ -8,13 +8,11 @@ import createFlash from '~/flash';
import TokenAccess from '~/token_access/components/token_access.vue';
import addProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
import removeProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql';
-import updateCIJobTokenScopeMutation from '~/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql';
import getCIJobTokenScopeQuery from '~/token_access/graphql/queries/get_ci_job_token_scope.query.graphql';
import getProjectsWithCIJobTokenScopeQuery from '~/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql';
import {
enabledJobTokenScope,
disabledJobTokenScope,
- updateJobTokenScope,
projectsWithScope,
addProjectSuccess,
removeProjectSuccess,
@@ -32,7 +30,6 @@ describe('TokenAccess component', () => {
const enabledJobTokenScopeHandler = jest.fn().mockResolvedValue(enabledJobTokenScope);
const disabledJobTokenScopeHandler = jest.fn().mockResolvedValue(disabledJobTokenScope);
- const updateJobTokenScopeHandler = jest.fn().mockResolvedValue(updateJobTokenScope);
const getProjectsWithScope = jest.fn().mockResolvedValue(projectsWithScope);
const addProjectSuccessHandler = jest.fn().mockResolvedValue(addProjectSuccess);
const addProjectFailureHandler = jest.fn().mockRejectedValue(error);
@@ -95,7 +92,7 @@ describe('TokenAccess component', () => {
expect(findTokenSection().exists()).toBe(true);
});
- it('the toggle should be disabled and the token section should not show', async () => {
+ it('the toggle should be disabled and the token section should show', async () => {
createComponent([
[getCIJobTokenScopeQuery, disabledJobTokenScopeHandler],
[getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
@@ -104,28 +101,7 @@ describe('TokenAccess component', () => {
await waitForPromises();
expect(findToggle().props('value')).toBe(false);
- expect(findTokenSection().exists()).toBe(false);
- });
-
- it('switching the toggle calls the mutation and fetches the projects again', async () => {
- createComponent([
- [getCIJobTokenScopeQuery, disabledJobTokenScopeHandler],
- [updateCIJobTokenScopeMutation, updateJobTokenScopeHandler],
- [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
- ]);
-
- await waitForPromises();
-
- expect(getProjectsWithScope).toHaveBeenCalledTimes(1);
-
- findToggle().vm.$emit('change', true);
-
- await waitForPromises();
-
- expect(updateJobTokenScopeHandler).toHaveBeenCalledWith({
- input: { fullPath: projectPath, jobTokenScopeEnabled: true },
- });
- expect(getProjectsWithScope).toHaveBeenCalledTimes(2);
+ expect(findTokenSection().exists()).toBe(true);
});
});
diff --git a/spec/frontend/tooltips/components/tooltips_spec.js b/spec/frontend/tooltips/components/tooltips_spec.js
index eef352a72ff..998bb2a9ea2 100644
--- a/spec/frontend/tooltips/components/tooltips_spec.js
+++ b/spec/frontend/tooltips/components/tooltips_spec.js
@@ -28,7 +28,7 @@ describe('tooltips/components/tooltips.vue', () => {
return target;
};
- const allTooltips = () => wrapper.findAll(GlTooltip);
+ const allTooltips = () => wrapper.findAllComponents(GlTooltip);
afterEach(() => {
wrapper.destroy();
@@ -68,7 +68,7 @@ describe('tooltips/components/tooltips.vue', () => {
await nextTick();
- expect(wrapper.findAll(GlTooltip)).toHaveLength(1);
+ expect(wrapper.findAllComponents(GlTooltip)).toHaveLength(1);
});
it('sets tooltip content from title attribute', async () => {
diff --git a/spec/frontend/user_lists/store/index/actions_spec.js b/spec/frontend/user_lists/store/index/actions_spec.js
index 4a8d0afb963..7b2e29ae412 100644
--- a/spec/frontend/user_lists/store/index/actions_spec.js
+++ b/spec/frontend/user_lists/store/index/actions_spec.js
@@ -41,7 +41,7 @@ describe('~/user_lists/store/index/actions', () => {
});
describe('success', () => {
- it('dispatches requestUserLists and receiveUserListsSuccess ', () => {
+ it('dispatches requestUserLists and receiveUserListsSuccess', () => {
return testAction(
fetchUserLists,
null,
@@ -61,7 +61,7 @@ describe('~/user_lists/store/index/actions', () => {
});
describe('error', () => {
- it('dispatches requestUserLists and receiveUserListsError ', () => {
+ it('dispatches requestUserLists and receiveUserListsError', () => {
Api.fetchFeatureFlagUserLists.mockRejectedValue();
return testAction(
diff --git a/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js b/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
index cb53dc1fb61..063425454d7 100644
--- a/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
@@ -1,10 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import AddedCommentMessage from '~/vue_merge_request_widget/components/added_commit_message.vue';
let wrapper;
function factory(propsData) {
- wrapper = shallowMount(AddedCommentMessage, {
+ wrapper = mount(AddedCommentMessage, {
propsData: {
isFastForwardEnabled: false,
targetBranch: 'main',
@@ -23,4 +23,13 @@ describe('Widget added commit message', () => {
expect(wrapper.element.outerHTML).toContain('The changes were not merged');
});
+
+ it('renders merge commit as a link', () => {
+ factory({ state: 'merged', mergeCommitPath: 'https://test.host/merge-commit-link' });
+
+ expect(wrapper.find('[data-testid="merge-commit-sha"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="merge-commit-sha"]').attributes('href')).toBe(
+ 'https://test.host/merge-commit-link',
+ );
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js b/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
index 712abfe228a..d519ad2cdb0 100644
--- a/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
@@ -39,10 +39,12 @@ describe('Artifacts List', () => {
});
it('renders job url', () => {
- expect(wrapper.findAll(GlLink).at(1).attributes('href')).toEqual(data.artifacts[0].job_path);
+ expect(wrapper.findAllComponents(GlLink).at(1).attributes('href')).toEqual(
+ data.artifacts[0].job_path,
+ );
});
it('renders job name', () => {
- expect(wrapper.findAll(GlLink).at(1).text()).toEqual(data.artifacts[0].job_name);
+ expect(wrapper.findAllComponents(GlLink).at(1).text()).toEqual(data.artifacts[0].job_name);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
index 6347e3c3be3..7f0173b7445 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
@@ -4,9 +4,8 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
-import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
-import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import MRWidgetPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import { SUCCESS } from '~/vue_merge_request_widget/constants';
import mockData from '../mock_data';
@@ -30,14 +29,13 @@ describe('MRWidgetPipeline', () => {
const findPipelineInfoContainer = () => wrapper.findByTestId('pipeline-info-container');
const findCommitLink = () => wrapper.findByTestId('commit-link');
const findPipelineFinishedAt = () => wrapper.findByTestId('finished-at');
- const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
- const findAllPipelineStages = () => wrapper.findAllComponents(PipelineStage);
const findPipelineCoverage = () => wrapper.findByTestId('pipeline-coverage');
const findPipelineCoverageDelta = () => wrapper.findByTestId('pipeline-coverage-delta');
const findPipelineCoverageTooltipText = () =>
wrapper.findByTestId('pipeline-coverage-tooltip').text();
const findPipelineCoverageDeltaTooltipText = () =>
wrapper.findByTestId('pipeline-coverage-delta-tooltip').text();
+ const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
const findMonitoringPipelineMessage = () => wrapper.findByTestId('monitoring-pipeline-message');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
@@ -45,7 +43,7 @@ describe('MRWidgetPipeline', () => {
const createWrapper = (props = {}, mountFn = shallowMount) => {
wrapper = extendedWrapper(
- mountFn(PipelineComponent, {
+ mountFn(MRWidgetPipelineComponent, {
propsData: {
...defaultProps,
...props,
@@ -106,8 +104,10 @@ describe('MRWidgetPipeline', () => {
});
it('should render pipeline graph', () => {
+ const stagesCount = mockData.pipeline.details.stages.length;
+
expect(findPipelineMiniGraph().exists()).toBe(true);
- expect(findAllPipelineStages()).toHaveLength(mockData.pipeline.details.stages.length);
+ expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
});
describe('should render pipeline coverage information', () => {
@@ -176,15 +176,11 @@ describe('MRWidgetPipeline', () => {
expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
});
- it('should render pipeline graph with correct styles', () => {
+ it('should render pipeline graph', () => {
const stagesCount = mockData.pipeline.details.stages.length;
expect(findPipelineMiniGraph().exists()).toBe(true);
- expect(findPipelineMiniGraph().findAll('.mr-widget-pipeline-stages')).toHaveLength(
- stagesCount,
- );
-
- expect(findAllPipelineStages()).toHaveLength(stagesCount);
+ expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
});
it('should render coverage information', () => {
@@ -266,13 +262,13 @@ describe('MRWidgetPipeline', () => {
});
describe('for a detached merge request pipeline', () => {
- it('renders a pipeline widget that reads "Detached merge request pipeline <ID> <status> for <SHA>"', () => {
- pipeline.details.name = 'Detached merge request pipeline';
+ it('renders a pipeline widget that reads "Merge request pipeline <ID> <status> for <SHA>"', () => {
+ pipeline.details.name = 'Merge request pipeline';
pipeline.merge_request_event_type = 'detached';
factory();
- const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
+ const expected = `Merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
index 534c0baf35d..05c259de370 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
@@ -110,7 +110,7 @@ describe('Merge request widget rebase component', () => {
expect(findRebaseMessageText()).toContain('Something went wrong!');
});
- describe('Rebase buttons with', () => {
+ describe('Rebase buttons', () => {
beforeEach(() => {
createWrapper(
{
@@ -148,6 +148,79 @@ describe('Merge request widget rebase component', () => {
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
});
});
+
+ describe('Rebase when pipelines must succeed is enabled', () => {
+ beforeEach(() => {
+ createWrapper(
+ {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ },
+ mergeRequestWidgetGraphql,
+ );
+ });
+
+ it('renders only the rebase button', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(false);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
+
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
+ });
+
+ describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => {
+ beforeEach(() => {
+ createWrapper(
+ {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ allowMergeOnSkippedPipeline: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ },
+ mergeRequestWidgetGraphql,
+ );
+ });
+
+ it('renders both rebase buttons', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(true);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
+
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
+
+ it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
+ findRebaseWithoutCiButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
+ });
+ });
});
describe('without permissions', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
index 11373be578a..530549b7b9c 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
@@ -1,14 +1,16 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
describe('MR widget status icon component', () => {
let wrapper;
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+ const findIcon = () => wrapper.findComponent(GlIcon);
- const createWrapper = (props, mountFn = shallowMount) => {
- wrapper = mountFn(mrStatusIcon, {
+ const createWrapper = (props) => {
+ wrapper = shallowMount(mrStatusIcon, {
propsData: {
...props,
},
@@ -17,27 +19,45 @@ describe('MR widget status icon component', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
describe('while loading', () => {
it('renders loading icon', () => {
createWrapper({ status: 'loading' });
- expect(findLoadingIcon().exists()).toBe(true);
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().isLoading).toBe(true);
});
});
describe('with status icon', () => {
it('renders success status icon', () => {
- createWrapper({ status: 'success' }, mount);
+ createWrapper({ status: 'success' });
- expect(wrapper.find('[data-testid="status_success-icon"]').exists()).toBe(true);
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().iconName).toBe('success');
});
it('renders failed status icon', () => {
- createWrapper({ status: 'failed' }, mount);
+ createWrapper({ status: 'failed' });
- expect(wrapper.find('[data-testid="status_failed-icon"]').exists()).toBe(true);
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().iconName).toBe('failed');
+ });
+
+ it('renders merged status icon', () => {
+ createWrapper({ status: 'merged' });
+
+ expect(findIcon().exists()).toBe(true);
+ expect(findIcon().props().name).toBe('merge');
+ });
+
+ it('renders closed status icon', () => {
+ createWrapper({ status: 'closed' });
+
+ expect(findIcon().exists()).toBe(true);
+ expect(findIcon().props().name).toBe('merge-request-close');
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
index 352bc1a08ea..d6c67dab381 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -128,7 +128,7 @@ describe('MRWidgetSuggestPipeline', () => {
it('emits dismiss upon dismissal button click', () => {
findDismissContainer().vm.$emit('dismiss');
- expect(wrapper.emitted().dismiss).toBeTruthy();
+ expect(wrapper.emitted().dismiss).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
index de25e2a0450..635ef0f6b0d 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
@@ -4,117 +4,171 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have
<div
class="mr-widget-body media"
>
- <svg
- aria-hidden="true"
- class="gl-text-blue-500 gl-mr-3 gl-mt-1 gl-icon s24"
- data-testid="status_scheduled-icon"
- role="img"
- >
- <use
- href="#status_scheduled"
- />
- </svg>
-
<div
- class="media-body gl-display-flex"
+ class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
>
-
- <h4
- class="gl-mr-3"
- data-testid="statusText"
+ <div
+ class="gl-display-flex gl-m-auto"
>
- Set by
- <a
- class="author-link inline"
+ <div
+ class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
>
- <img
- class="avatar avatar-inline s16"
- src="no_avatar.png"
- />
-
- <span
- class="author"
+ <div
+ class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
>
-
- </span>
- </a>
- to be merged automatically when the pipeline succeeds
- </h4>
-
+ <div
+ class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
+ >
+ <div
+ class="gl-display-flex gl-m-auto gl-translate-y-n50"
+ >
+ <svg
+ aria-label="Scheduled "
+ class="gl-display-block gl-icon s12"
+ data-qa-selector="status_scheduled_icon"
+ data-testid="status-scheduled-icon"
+ role="img"
+ >
+ <use
+ href="#status-scheduled"
+ />
+ </svg>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex gl-w-full"
+ >
<div
- class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
+ class="media-body gl-display-flex"
>
- <div>
- <div
- class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
- lazy=""
- no-caret=""
+
+ <h4
+ class="gl-mr-3"
+ data-testid="statusText"
+ >
+ Set by
+ <a
+ class="author-link inline"
>
- <!---->
+ <img
+ class="avatar avatar-inline s16"
+ src="no_avatar.png"
+ />
+
+ <span
+ class="author"
+ >
+
+ </span>
+ </a>
+ to be merged automatically when the pipeline succeeds
+ </h4>
+
+ <div
+ class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
+ >
+ <div>
+ <div
+ class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
+ lazy=""
+ no-caret=""
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ type="button"
+ >
+ <!---->
+
+ <svg
+ aria-hidden="true"
+ class="dropdown-icon gl-icon s16"
+ data-testid="ellipsis_v-icon"
+ role="img"
+ >
+ <use
+ href="#ellipsis_v"
+ />
+ </svg>
+
+ <span
+ class="gl-new-dropdown-button-text gl-sr-only"
+ >
+
+ </span>
+
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ role="img"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+ </ul>
+ </div>
+
<button
- aria-expanded="false"
- aria-haspopup="true"
- class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
+ data-qa-selector="cancel_auto_merge_button"
+ data-testid="cancelAutomaticMergeButton"
type="button"
>
<!---->
- <svg
- aria-hidden="true"
- class="dropdown-icon gl-icon s16"
- data-testid="ellipsis_v-icon"
- role="img"
- >
- <use
- href="#ellipsis_v"
- />
- </svg>
-
+ <!---->
+
<span
- class="gl-new-dropdown-button-text gl-sr-only"
+ class="gl-button-text"
>
+ Cancel auto-merge
+
</span>
-
- <svg
- aria-hidden="true"
- class="gl-button-icon dropdown-chevron gl-icon s16"
- data-testid="chevron-down-icon"
- role="img"
- >
- <use
- href="#chevron-down"
- />
- </svg>
</button>
- <ul
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- tabindex="-1"
- >
- <!---->
- </ul>
</div>
+ </div>
+ </div>
+
+ <div
+ class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1"
+ >
+ <button
+ class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon"
+ title="Collapse merge details"
+ type="button"
+ >
+ <!---->
- <button
- class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
- data-qa-selector="cancel_auto_merge_button"
- data-testid="cancelAutomaticMergeButton"
- type="button"
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon gl-icon s16"
+ data-testid="chevron-lg-up-icon"
+ role="img"
>
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Cancel auto-merge
-
- </span>
- </button>
- </div>
+ <use
+ href="#chevron-lg-up"
+ />
+ </svg>
+
+ <!---->
+ </button>
</div>
</div>
</div>
@@ -124,117 +178,171 @@ exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have c
<div
class="mr-widget-body media"
>
- <svg
- aria-hidden="true"
- class="gl-text-blue-500 gl-mr-3 gl-mt-1 gl-icon s24"
- data-testid="status_scheduled-icon"
- role="img"
- >
- <use
- href="#status_scheduled"
- />
- </svg>
-
<div
- class="media-body gl-display-flex"
+ class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
>
-
- <h4
- class="gl-mr-3"
- data-testid="statusText"
+ <div
+ class="gl-display-flex gl-m-auto"
>
- Set by
- <a
- class="author-link inline"
+ <div
+ class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
>
- <img
- class="avatar avatar-inline s16"
- src="no_avatar.png"
- />
-
- <span
- class="author"
+ <div
+ class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
>
-
- </span>
- </a>
- to be merged automatically when the pipeline succeeds
- </h4>
-
+ <div
+ class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
+ >
+ <div
+ class="gl-display-flex gl-m-auto gl-translate-y-n50"
+ >
+ <svg
+ aria-label="Scheduled "
+ class="gl-display-block gl-icon s12"
+ data-qa-selector="status_scheduled_icon"
+ data-testid="status-scheduled-icon"
+ role="img"
+ >
+ <use
+ href="#status-scheduled"
+ />
+ </svg>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex gl-w-full"
+ >
<div
- class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
+ class="media-body gl-display-flex"
>
- <div>
- <div
- class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
- lazy=""
- no-caret=""
+
+ <h4
+ class="gl-mr-3"
+ data-testid="statusText"
+ >
+ Set by
+ <a
+ class="author-link inline"
>
- <!---->
+ <img
+ class="avatar avatar-inline s16"
+ src="no_avatar.png"
+ />
+
+ <span
+ class="author"
+ >
+
+ </span>
+ </a>
+ to be merged automatically when the pipeline succeeds
+ </h4>
+
+ <div
+ class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
+ >
+ <div>
+ <div
+ class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
+ lazy=""
+ no-caret=""
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ type="button"
+ >
+ <!---->
+
+ <svg
+ aria-hidden="true"
+ class="dropdown-icon gl-icon s16"
+ data-testid="ellipsis_v-icon"
+ role="img"
+ >
+ <use
+ href="#ellipsis_v"
+ />
+ </svg>
+
+ <span
+ class="gl-new-dropdown-button-text gl-sr-only"
+ >
+
+ </span>
+
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ role="img"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+ </ul>
+ </div>
+
<button
- aria-expanded="false"
- aria-haspopup="true"
- class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
+ data-qa-selector="cancel_auto_merge_button"
+ data-testid="cancelAutomaticMergeButton"
type="button"
>
<!---->
- <svg
- aria-hidden="true"
- class="dropdown-icon gl-icon s16"
- data-testid="ellipsis_v-icon"
- role="img"
- >
- <use
- href="#ellipsis_v"
- />
- </svg>
-
+ <!---->
+
<span
- class="gl-new-dropdown-button-text gl-sr-only"
+ class="gl-button-text"
>
+ Cancel auto-merge
+
</span>
-
- <svg
- aria-hidden="true"
- class="gl-button-icon dropdown-chevron gl-icon s16"
- data-testid="chevron-down-icon"
- role="img"
- >
- <use
- href="#chevron-down"
- />
- </svg>
</button>
- <ul
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- tabindex="-1"
- >
- <!---->
- </ul>
</div>
+ </div>
+ </div>
+
+ <div
+ class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1"
+ >
+ <button
+ class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon"
+ title="Collapse merge details"
+ type="button"
+ >
+ <!---->
- <button
- class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
- data-qa-selector="cancel_auto_merge_button"
- data-testid="cancelAutomaticMergeButton"
- type="button"
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon gl-icon s16"
+ data-testid="chevron-lg-up-icon"
+ role="img"
>
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Cancel auto-merge
-
- </span>
- </button>
- </div>
+ <use
+ href="#chevron-lg-up"
+ />
+ </svg>
+
+ <!---->
+ </button>
</div>
</div>
</div>
diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
deleted file mode 100644
index 7e741bf4660..00000000000
--- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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
- show-disabled-button="true"
- status="warning"
- />
-
- <div
- class="media-body space-children"
- >
- <span
- class="gl-ml-0! gl-text-body! bold"
- >
- <gl-sprintf-stub
- message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}"
- />
- </span>
- </div>
-</div>
-`;
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
index 9332b7e334a..5c07f4ce143 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
@@ -1,25 +1,26 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
describe('MRWidgetArchived', () => {
- let vm;
+ let wrapper;
beforeEach(() => {
- const Component = Vue.extend(archivedComponent);
- vm = mountComponent(Component);
+ wrapper = shallowMount(archivedComponent, { propsData: { mr: {} } });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- it('renders a ci status failed icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull();
+ it('renders error icon', () => {
+ expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(StateContainer).props().status).toBe('failed');
});
- it('renders information', () => {
- expect(vm.$el.querySelector('.bold').textContent.trim()).toEqual(
+ it('renders information about merging', () => {
+ expect(wrapper.text()).toContain(
'Merge unavailable: merge requests are read-only on archived projects.',
);
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
index 02de426204b..ac18ccf9e26 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
@@ -1,27 +1,25 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import { shallowMount } from '@vue/test-utils';
+import CheckingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
describe('MRWidgetChecking', () => {
- let Component;
- let vm;
+ let wrapper;
beforeEach(() => {
- Component = Vue.extend(checkingComponent);
- vm = mountComponent(Component);
+ wrapper = shallowMount(CheckingComponent, { propsData: { mr: {} } });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
+ expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(StateContainer).props().status).toBe('loading');
});
it('renders information about merging', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual(
- 'Checking if merge request can be merged…',
- );
+ expect(wrapper.text()).toContain('Checking if merge request can be merged…');
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js
index f7d046eb8f9..06ee017dee7 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js
@@ -1,39 +1,54 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
+import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
+
+const MOCK_DATA = {
+ metrics: {
+ mergedBy: {},
+ closedBy: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ mergedAt: 'Jan 24, 2018 1:02pm UTC',
+ closedAt: 'Jan 24, 2018 1:02pm UTC',
+ readableMergedAt: '',
+ readableClosedAt: 'less than a minute ago',
+ },
+ targetBranchPath: '/twitter/flight/commits/so_long_jquery',
+ targetBranch: 'so_long_jquery',
+};
describe('MRWidgetClosed', () => {
- let vm;
+ let wrapper;
beforeEach(() => {
- const Component = Vue.extend(closedComponent);
- vm = mountComponent(Component, {
- mr: {
- metrics: {
- mergedBy: {},
- closedBy: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'http://localhost:3000/root',
- avatarUrl:
- 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- },
- mergedAt: 'Jan 24, 2018 1:02pm UTC',
- closedAt: 'Jan 24, 2018 1:02pm UTC',
- readableMergedAt: '',
- readableClosedAt: 'less than a minute ago',
- },
- targetBranchPath: '/twitter/flight/commits/so_long_jquery',
- targetBranch: 'so_long_jquery',
+ wrapper = shallowMount(closedComponent, {
+ propsData: {
+ mr: MOCK_DATA,
},
});
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders closed icon', () => {
+ expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(StateContainer).props().status).toBe('closed');
});
- it('renders warning icon', () => {
- expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
+ it('renders mr widget author time', () => {
+ expect(wrapper.findComponent(MrWidgetAuthorTime).exists()).toBe(true);
+ expect(wrapper.findComponent(MrWidgetAuthorTime).props()).toEqual({
+ actionText: 'Closed by',
+ author: MOCK_DATA.metrics.closedBy,
+ dateTitle: MOCK_DATA.metrics.closedAt,
+ dateReadable: MOCK_DATA.metrics.readableClosedAt,
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index 663fabb761c..5d2d1fdd6f1 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -40,7 +40,7 @@ describe('Commits message dropdown component', () => {
wrapper.destroy();
});
- const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
+ const findDropdownElements = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownElement = () => findDropdownElements().at(0);
it('should have 3 elements in dropdown list', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
index 989aa76f09b..833fa27d453 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import MrWidgetFailedToMerge from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
describe('MRWidgetFailedToMerge', () => {
@@ -39,7 +40,7 @@ describe('MRWidgetFailedToMerge', () => {
expect(wrapper.vm.intervalId).toBe(dummyIntervalId);
});
- it('clears interval when destroying ', () => {
+ it('clears interval when destroying', () => {
createComponent();
wrapper.destroy();
@@ -128,7 +129,11 @@ describe('MRWidgetFailedToMerge', () => {
await nextTick();
- expect(wrapper.find('.js-refresh-label').text().trim()).toBe('Refreshing now');
+ const stateContainerWrapper = wrapper.findComponent(StateContainer);
+
+ expect(stateContainerWrapper.exists()).toBe(true);
+ expect(stateContainerWrapper.props('status')).toBe('loading');
+ expect(stateContainerWrapper.text().trim()).toBe('Refreshing now');
});
});
@@ -146,9 +151,9 @@ describe('MRWidgetFailedToMerge', () => {
});
it('renders refresh button', () => {
- expect(
- wrapper.find('[data-testid="merge-request-failed-refresh-button"]').text().trim(),
- ).toBe('Refresh now');
+ expect(wrapper.findComponent(StateContainer).props('actions')).toMatchObject([
+ { text: 'Refresh now', onClick: expect.any(Function) },
+ ]);
});
it('renders remaining time', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
index 63e93074857..c6e7198c678 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
@@ -1,25 +1,27 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('MRWidgetNotAllowed', () => {
- let vm;
+ let wrapper;
+
beforeEach(() => {
- const Component = Vue.extend(notAllowedComponent);
- vm = mountComponent(Component);
+ wrapper = shallowMount(notAllowedComponent);
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('renders success icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon-success')).not.toBe(null);
+ expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(StatusIcon).props().status).toBe('success');
});
it('renders informative text', () => {
- expect(vm.$el.innerText).toContain('Ready to be merged automatically.');
- expect(vm.$el.innerText).toContain(
+ expect(wrapper.text()).toContain('Ready to be merged automatically.');
+ expect(wrapper.text()).toContain(
'Ask someone with write access to this repository to merge this request',
);
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
index 9b10b078e89..4219ad70b4c 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
@@ -1,26 +1,25 @@
-import { shallowMount, mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import PipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('MRWidgetPipelineBlocked', () => {
let wrapper;
- const createWrapper = (mountFn = shallowMount) => {
- wrapper = mountFn(PipelineBlockedComponent);
- };
+ beforeEach(() => {
+ wrapper = shallowMount(PipelineBlockedComponent);
+ });
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
- it('renders warning icon', () => {
- createWrapper(mount);
-
- expect(wrapper.find('.ci-status-icon-warning').exists()).toBe(true);
+ it('renders error icon', () => {
+ expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
});
it('renders information text', () => {
- createWrapper();
-
expect(wrapper.text()).toBe(
"Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.",
);
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
index 4e44ac539f2..d5619d4996d 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
@@ -1,11 +1,17 @@
+import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('PipelineFailed', () => {
let wrapper;
const createComponent = () => {
- wrapper = shallowMount(PipelineFailed);
+ wrapper = shallowMount(PipelineFailed, {
+ stubs: {
+ GlSprintf,
+ },
+ });
};
beforeEach(() => {
@@ -17,7 +23,14 @@ describe('PipelineFailed', () => {
wrapper = null;
});
+ it('should render error status icon', () => {
+ expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
+ });
+
it('should render error message with a disabled merge button', () => {
- expect(wrapper.element).toMatchSnapshot();
+ expect(wrapper.text()).toContain('Merge blocked: pipeline must succeed.');
+ expect(wrapper.text()).toContain('Push a commit that fixes the failure');
+ expect(wrapper.findComponent(GlLink).text()).toContain('learn about other solutions');
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
index 6e89cd41559..9a6bf66909e 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -111,7 +111,7 @@ const createComponent = (
};
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
-const findCommitEditElements = () => wrapper.findAll(CommitEdit);
+const findCommitEditElements = () => wrapper.findAllComponents(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label');
const findTipLink = () => wrapper.find(GlSprintf);
@@ -549,7 +549,7 @@ describe('ReadyToMerge', () => {
${'squashIsSelected'} | ${'selected'} | ${'value'} | ${false}
${'squashIsSelected'} | ${'unselected'} | ${'value'} | ${false}
`(
- 'is $state when squashIsReadonly returns $expectation ',
+ 'is $state when squashIsReadonly returns $expectation',
({ squashState, prop, expectation }) => {
createComponent({
mr: { commitsCount: 2, enableSquashBeforeMerge: true, [squashState]: expectation },
diff --git a/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js
index 8f20d6a8fc9..7a868eb8cc9 100644
--- a/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -16,7 +16,8 @@ describe('MrWidgetTerraformConainer', () => {
const propsData = { endpoint: '/path/to/terraform/report.json' };
const findHeader = () => wrapper.find('[data-testid="terraform-header-text"]');
- const findPlans = () => wrapper.findAll(TerraformPlan).wrappers.map((x) => x.props('plan'));
+ const findPlans = () =>
+ wrapper.findAllComponents(TerraformPlan).wrappers.map((x) => x.props('plan'));
const mockPollingApi = (response, body, header) => {
mock.onGet(propsData.endpoint).reply(response, body, header);
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js
index 6bb718082a4..8dbee9b370c 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js
@@ -12,8 +12,8 @@ describe('MR Widget App', () => {
});
};
- it('mounts the component', () => {
+ it('does not mount if widgets array is empty', () => {
createComponent();
- expect(wrapper.findByTestId('mr-widget-app').exists()).toBe(true);
+ expect(wrapper.findByTestId('mr-widget-app').exists()).toBe(false);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js
new file mode 100644
index 00000000000..c2128d3ff33
--- /dev/null
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js
@@ -0,0 +1,39 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WidgetContentSection from '~/vue_merge_request_widget/components/widget/widget_content_section.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
+
+describe('~/vue_merge_request_widget/components/widget/widget_content_section.vue', () => {
+ let wrapper;
+
+ const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+
+ const createComponent = ({ propsData, slots } = {}) => {
+ wrapper = shallowMountExtended(WidgetContentSection, {
+ propsData: {
+ widgetName: 'MyWidget',
+ ...propsData,
+ },
+ slots,
+ });
+ };
+
+ it('does not render the status icon when it is not provided', () => {
+ createComponent();
+ expect(findStatusIcon().exists()).toBe(false);
+ });
+
+ it('renders the status icon when provided', () => {
+ createComponent({ propsData: { statusIconName: 'failed' } });
+ expect(findStatusIcon().exists()).toBe(true);
+ });
+
+ it('renders the default slot', () => {
+ createComponent({
+ slots: {
+ default: 'Hello world',
+ },
+ });
+
+ expect(wrapper.findByText('Hello world').exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
index 3c08ffdef18..b67b5703ad5 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
@@ -3,16 +3,21 @@ import * as Sentry from '@sentry/browser';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
+import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue';
import Widget from '~/vue_merge_request_widget/components/widget/widget.vue';
describe('MR Widget', () => {
let wrapper;
const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+ const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section');
+ const findActionButtons = () => wrapper.findComponent(ActionButtons);
+ const findToggleButton = () => wrapper.findByTestId('toggle-button');
const createComponent = ({ propsData, slots } = {}) => {
wrapper = shallowMountExtended(Widget, {
propsData: {
+ isCollapsible: false,
loadingText: 'Loading widget',
widgetName: 'MyWidget',
value: {
@@ -38,14 +43,15 @@ describe('MR Widget', () => {
createComponent({ propsData: { fetchCollapsedData } });
await waitForPromises();
expect(fetchCollapsedData).toHaveBeenCalled();
- expect(wrapper.vm.error).toBe(null);
+ expect(wrapper.vm.summaryError).toBe(null);
});
it('sets the error text when fetch method fails', async () => {
const fetchCollapsedData = jest.fn().mockReturnValue(() => Promise.reject());
createComponent({ propsData: { fetchCollapsedData } });
await waitForPromises();
- expect(wrapper.vm.error).toBe('Failed to load');
+ expect(wrapper.findByText('Failed to load').exists()).toBe(true);
+ expect(findStatusIcon().props()).toMatchObject({ iconName: 'failed', isLoading: false });
});
it('displays loading icon until request is made and then displays status icon when the request is complete', async () => {
@@ -111,7 +117,7 @@ describe('MR Widget', () => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
createComponent({
propsData: {
- fetchCollapsedData: async () => Promise.reject(error),
+ fetchCollapsedData: () => Promise.reject(error),
},
});
await waitForPromises();
@@ -125,7 +131,7 @@ describe('MR Widget', () => {
createComponent({
propsData: {
summary: 'Hello world',
- fetchCollapsedData: async () => Promise.resolve(),
+ fetchCollapsedData: () => Promise.resolve(),
},
});
@@ -137,7 +143,7 @@ describe('MR Widget', () => {
it('displays the summary slot when provided', () => {
createComponent({
propsData: {
- fetchCollapsedData: async () => Promise.resolve(),
+ fetchCollapsedData: () => Promise.resolve(),
},
slots: {
summary: '<b>More complex summary</b>',
@@ -149,19 +155,167 @@ describe('MR Widget', () => {
);
});
- it('displays the content slot when provided', () => {
+ it('does not display action buttons if actionButtons is not provided', () => {
createComponent({
propsData: {
- fetchCollapsedData: async () => Promise.resolve(),
+ fetchCollapsedData: () => Promise.resolve(),
+ },
+ });
+
+ expect(findActionButtons().exists()).toBe(false);
+ });
+
+ it('does display action buttons if actionButtons is provided', () => {
+ const actionButtons = [{ text: 'click-me', href: '#' }];
+
+ createComponent({
+ propsData: {
+ fetchCollapsedData: () => Promise.resolve(),
+ actionButtons,
+ },
+ });
+
+ expect(findActionButtons().props('tertiaryButtons')).toEqual(actionButtons);
+ });
+ });
+
+ describe('handle collapse toggle', () => {
+ it('displays the toggle button correctly', () => {
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve(),
},
slots: {
content: '<b>More complex content</b>',
},
});
- expect(wrapper.findByTestId('widget-extension-collapsed-section').text()).toBe(
- 'More complex content',
- );
+ expect(findToggleButton().attributes('title')).toBe('Show details');
+ expect(findToggleButton().attributes('aria-label')).toBe('Show details');
+ });
+
+ it('does not display the content slot until toggle is clicked', async () => {
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve(),
+ },
+ slots: {
+ content: '<b>More complex content</b>',
+ },
+ });
+
+ expect(findExpandedSection().exists()).toBe(false);
+ findToggleButton().vm.$emit('click');
+ await nextTick();
+ expect(findExpandedSection().text()).toBe('More complex content');
+ });
+
+ it('does not display the toggle button if isCollapsible is false', () => {
+ createComponent({
+ propsData: {
+ isCollapsible: false,
+ fetchCollapsedData: () => Promise.resolve(),
+ },
+ });
+
+ expect(findToggleButton().exists()).toBe(false);
+ });
+
+ it('fetches expanded data when clicked for the first time', async () => {
+ const mockDataCollapsed = {
+ headers: {},
+ status: 200,
+ data: { vulnerabilities: [{ vuln: 1 }] },
+ };
+
+ const mockDataExpanded = {
+ headers: {},
+ status: 200,
+ data: { vulnerabilities: [{ vuln: 2 }] },
+ };
+
+ const fetchExpandedData = jest.fn().mockResolvedValue(mockDataExpanded);
+
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve(mockDataCollapsed),
+ fetchExpandedData,
+ },
+ });
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+
+ // First fetches the collapsed data
+ expect(wrapper.emitted('input')[0][0]).toEqual({
+ collapsed: mockDataCollapsed.data,
+ expanded: null,
+ });
+
+ // Then fetches the expanded data
+ expect(wrapper.emitted('input')[1][0]).toEqual({
+ collapsed: null,
+ expanded: mockDataExpanded.data,
+ });
+
+ // Triggering a click does not call the expanded data again
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+ expect(fetchExpandedData).toHaveBeenCalledTimes(1);
+ });
+
+ it('allows refetching when fetch expanded data returns an error', async () => {
+ const fetchExpandedData = jest.fn().mockRejectedValue({ error: true });
+
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve([]),
+ fetchExpandedData,
+ },
+ });
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+
+ // First fetches the collapsed data
+ expect(wrapper.emitted('input')[0][0]).toEqual({
+ collapsed: undefined,
+ expanded: null,
+ });
+
+ expect(fetchExpandedData).toHaveBeenCalledTimes(1);
+ expect(wrapper.emitted('input')).toHaveLength(1); // Should not an emit an input call because request failed
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+ expect(fetchExpandedData).toHaveBeenCalledTimes(2);
+ });
+
+ it('resets the error message when another request is fetched', async () => {
+ const fetchExpandedData = jest.fn().mockRejectedValue({ error: true });
+
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve([]),
+ fetchExpandedData,
+ },
+ });
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+
+ expect(wrapper.findByText('Failed to load').exists()).toBe(true);
+ fetchExpandedData.mockImplementation(() => new Promise(() => {}));
+
+ findToggleButton().vm.$emit('click');
+ await nextTick();
+
+ expect(wrapper.findByText('Failed to load').exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
index a285d26f404..a8912405fa8 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
@@ -189,7 +189,7 @@ describe('DeploymentAction component', () => {
});
});
- describe('it should call the executeAction method ', () => {
+ describe('it should call the executeAction method', () => {
beforeEach(async () => {
jest.spyOn(wrapper.vm, 'executeAction').mockImplementation();
diff --git a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
index 5c1d3c8e8e8..82743275739 100644
--- a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
@@ -15,6 +15,7 @@ import { failedReport } from 'jest/reports/mock_data/mock_data';
import mixedResultsTestReports from 'jest/reports/mock_data/new_and_fixed_failures_report.json';
import newErrorsTestReports from 'jest/reports/mock_data/new_errors_report.json';
import newFailedTestReports from 'jest/reports/mock_data/new_failures_report.json';
+import newFailedTestWithNullFilesReport from 'jest/reports/mock_data/new_failures_with_null_files_report.json';
import successTestReports from 'jest/reports/mock_data/no_failures_report.json';
import resolvedFailures from 'jest/reports/mock_data/resolved_failures.json';
import recentFailures from 'jest/reports/mock_data/recent_failures_report.json';
@@ -157,6 +158,15 @@ describe('Test report extension', () => {
);
});
+ it('hides copy failed tests button when endpoint returns null files', async () => {
+ mockApi(httpStatusCodes.OK, newFailedTestWithNullFilesReport);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findCopyFailedSpecsBtn().exists()).toBe(false);
+ });
+
it('copy failed tests button updates tooltip text when clicked', async () => {
mockApi(httpStatusCodes.OK, newFailedTestReports);
createComponent();
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index 819841317f9..cc894f94f80 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -845,7 +845,7 @@ describe('MrWidgetOptions', () => {
${'closed'} | ${false} | ${'hides'}
${'merged'} | ${true} | ${'shows'}
${'open'} | ${true} | ${'shows'}
- `('it $showText merge error when state is $state', ({ state, show }) => {
+ `('$showText merge error when state is $state', ({ state, show }) => {
createComponent({ ...mockData, state, merge_error: 'Error!' });
expect(wrapper.find('[data-testid="merge_error"]').exists()).toBe(show);
@@ -1133,7 +1133,7 @@ describe('MrWidgetOptions', () => {
widgetName | nonStandardEvent
${'WidgetCodeQuality'} | ${'i_testing_code_quality_widget_total'}
${'WidgetTerraform'} | ${'i_testing_terraform_widget_total'}
- ${'WidgetIssues'} | ${'i_testing_load_performance_widget_total'}
+ ${'WidgetIssues'} | ${'i_testing_issues_widget_total'}
${'WidgetTestReport'} | ${'i_testing_summary_widget_total'}
`(
"sends non-standard events for the '$widgetName' widget",
diff --git a/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js b/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js
index 22562bb4ddb..1a109aad911 100644
--- a/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js
@@ -60,7 +60,7 @@ describe('Artifacts App Store Actions', () => {
});
describe('success', () => {
- it('dispatches requestArtifacts and receiveArtifactsSuccess ', () => {
+ it('dispatches requestArtifacts and receiveArtifactsSuccess', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [
{
text: 'result.txt',
@@ -103,7 +103,7 @@ describe('Artifacts App Store Actions', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
- it('dispatches requestArtifacts and receiveArtifactsError ', () => {
+ it('dispatches requestArtifacts and receiveArtifactsError', () => {
return testAction(
fetchArtifacts,
null,
diff --git a/spec/frontend/vue_shared/alert_details/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
index 59e21b2ff40..d309432bc63 100644
--- a/spec/frontend/vue_shared/alert_details/alert_details_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
@@ -248,7 +248,7 @@ describe('AlertDetails', () => {
});
});
- it('shows error alert when incident creation fails ', async () => {
+ it('shows error alert when incident creation fails', async () => {
const errorMsg = 'Something went wrong';
mountComponent({
mountMethod: mount,
diff --git a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
index cf04c1eb24a..9d84a535d67 100644
--- a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
@@ -42,7 +42,7 @@ describe('Alert Metrics', () => {
});
describe('Empty state', () => {
- it('should display a message when metrics dashboard url is not provided ', () => {
+ it('should display a message when metrics dashboard url is not provided', () => {
mountComponent();
expect(findChart().exists()).toBe(false);
expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload.");
diff --git a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap
deleted file mode 100644
index 7f655d67ae8..00000000000
--- a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap
+++ /dev/null
@@ -1,26 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Code Block with default props renders correctly 1`] = `
-<pre
- class="code-block rounded code"
->
- <code
- class="d-block"
- >
- test-code
- </code>
-</pre>
-`;
-
-exports[`Code Block with maxHeight set to "200px" renders correctly 1`] = `
-<pre
- class="code-block rounded code"
- style="max-height: 200px; overflow-y: auto;"
->
- <code
- class="d-block"
- >
- test-code
- </code>
-</pre>
-`;
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 a943d931f67..27b6718fb8e 100644
--- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
@@ -1,6 +1,11 @@
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';
+import { visitUrl } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+}));
describe('CI Badge Link Component', () => {
let wrapper;
@@ -79,17 +84,20 @@ describe('CI Badge Link Component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- it.each(Object.keys(statuses))('should render badge for status: %s', (status) => {
+ it.each(Object.keys(statuses))('should render badge for status: %s', async (status) => {
createComponent({ status: statuses[status] });
- expect(wrapper.attributes('href')).toBe(statuses[status].details_path);
+ expect(wrapper.attributes('href')).toBe();
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);
+
+ await wrapper.trigger('click');
+
+ expect(visitUrl).toHaveBeenCalledWith(statuses[status].details_path);
});
it('should not render label', () => {
@@ -97,4 +105,12 @@ describe('CI Badge Link Component', () => {
expect(wrapper.text()).toBe('');
});
+
+ it('should emit ciStatusBadgeClick event', async () => {
+ createComponent({ status: statuses.success });
+
+ await wrapper.trigger('click');
+
+ expect(wrapper.emitted('ciStatusBadgeClick')).toEqual([[]]);
+ });
});
diff --git a/spec/frontend/vue_shared/components/code_block_highlighted_spec.js b/spec/frontend/vue_shared/components/code_block_highlighted_spec.js
new file mode 100644
index 00000000000..181692e61b5
--- /dev/null
+++ b/spec/frontend/vue_shared/components/code_block_highlighted_spec.js
@@ -0,0 +1,65 @@
+import { shallowMount } from '@vue/test-utils';
+import CodeBlock from '~/vue_shared/components/code_block_highlighted.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+
+describe('Code Block Highlighted', () => {
+ let wrapper;
+
+ const code = 'const foo = 1;';
+
+ const createComponent = (propsData = {}) => {
+ wrapper = shallowMount(CodeBlock, { propsData });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders highlighted code if language is supported', async () => {
+ createComponent({ code, language: 'javascript' });
+
+ await waitForPromises();
+
+ expect(wrapper.element).toMatchInlineSnapshot(`
+ <code-block-stub
+ class="highlight"
+ code=""
+ maxheight="initial"
+ >
+ <span>
+ <span
+ class="hljs-keyword"
+ >
+ const
+ </span>
+ foo =
+ <span
+ class="hljs-number"
+ >
+ 1
+ </span>
+ ;
+ </span>
+ </code-block-stub>
+ `);
+ });
+
+ it("renders plain text if language isn't supported", async () => {
+ createComponent({ code, language: 'foobar' });
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[expect.any(TypeError)]]);
+
+ expect(wrapper.element).toMatchInlineSnapshot(`
+ <code-block-stub
+ class="highlight"
+ code=""
+ maxheight="initial"
+ >
+ <span>
+ const foo = 1;
+ </span>
+ </code-block-stub>
+ `);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/code_block_spec.js b/spec/frontend/vue_shared/components/code_block_spec.js
index 60b0b0b566b..9a4dbcc47ff 100644
--- a/spec/frontend/vue_shared/components/code_block_spec.js
+++ b/spec/frontend/vue_shared/components/code_block_spec.js
@@ -4,41 +4,77 @@ import CodeBlock from '~/vue_shared/components/code_block.vue';
describe('Code Block', () => {
let wrapper;
- const defaultProps = {
- code: 'test-code',
- };
+ const code = 'test-code';
- const createComponent = (props = {}) => {
+ const createComponent = (propsData, slots = {}) => {
wrapper = shallowMount(CodeBlock, {
- propsData: {
- ...defaultProps,
- ...props,
- },
+ slots,
+ propsData,
});
};
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- describe('with default props', () => {
- beforeEach(() => {
- createComponent();
- });
+ it('overwrites the default slot', () => {
+ createComponent({}, { default: 'DEFAULT SLOT' });
- it('renders correctly', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ expect(wrapper.element).toMatchInlineSnapshot(`
+ <pre
+ class="code-block rounded code"
+ >
+ DEFAULT SLOT
+ </pre>
+ `);
});
- describe('with maxHeight set to "200px"', () => {
- beforeEach(() => {
- createComponent({ maxHeight: '200px' });
- });
+ it('renders with empty code prop', () => {
+ createComponent({});
- it('renders correctly', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ expect(wrapper.element).toMatchInlineSnapshot(`
+ <pre
+ class="code-block rounded code"
+ >
+ <code
+ class="d-block"
+ >
+
+ </code>
+ </pre>
+ `);
+ });
+
+ it('renders code prop when provided', () => {
+ createComponent({ code });
+
+ expect(wrapper.element).toMatchInlineSnapshot(`
+ <pre
+ class="code-block rounded code"
+ >
+ <code
+ class="d-block"
+ >
+ test-code
+ </code>
+ </pre>
+ `);
+ });
+
+ it('sets maxHeight properly when provided', () => {
+ createComponent({ code, maxHeight: '200px' });
+
+ expect(wrapper.element).toMatchInlineSnapshot(`
+ <pre
+ class="code-block rounded code"
+ style="max-height: 200px; overflow-y: auto;"
+ >
+ <code
+ class="d-block"
+ >
+ test-code
+ </code>
+ </pre>
+ `);
});
});
diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
index 04f63b4bd45..68684004b82 100644
--- a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
@@ -66,7 +66,7 @@ describe('Diff Stats Dropdown', () => {
createComponent({ files: mockFiles });
});
- it('when no file name provided ', () => {
+ it('when no file name provided', () => {
expect(findChangedFiles().at(0).text()).toContain(i18n.noFileNameAvailable);
});
@@ -153,7 +153,7 @@ describe('Diff Stats Dropdown', () => {
createComponent({ files: mockFiles });
});
- it('updates the URL ', () => {
+ it('updates the URL', () => {
findChangedFiles().at(0).vm.$emit('click');
expect(window.location.hash).toBe(mockFiles[0].href);
findChangedFiles().at(1).vm.$emit('click');
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 2dcd91f737f..6dc018797a6 100644
--- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
@@ -157,13 +157,13 @@ describe('GlModalVuex', () => {
const handler = modalFooterSlotContent.mock.calls[0][0][handlerName];
- expect(wrapper.emitted(handlerName)).toBeFalsy();
+ expect(wrapper.emitted(handlerName)).toBeUndefined();
expect(actions.hide).not.toHaveBeenCalled();
handler();
expect(actions.hide).toHaveBeenCalledTimes(1);
- expect(wrapper.emitted(handlerName)).toBeTruthy();
+ expect(wrapper.emitted(handlerName)).toHaveLength(1);
},
);
});
diff --git a/spec/frontend/vue_shared/components/paginated_list_spec.js b/spec/frontend/vue_shared/components/paginated_list_spec.js
index 9f819cc4e94..ae9c920ebd2 100644
--- a/spec/frontend/vue_shared/components/paginated_list_spec.js
+++ b/spec/frontend/vue_shared/components/paginated_list_spec.js
@@ -49,7 +49,7 @@ describe('Pagination links component', () => {
});
describe('rendering', () => {
- it('it renders the gl-paginated-list', () => {
+ it('renders the gl-paginated-list', () => {
expect(wrapper.find('ul.list-group').exists()).toBe(true);
expect(wrapper.findAll('li.list-group-item').length).toBe(2);
});
diff --git a/spec/frontend/vue_shared/components/registry/registry_search_spec.js b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
index 70f4693ae81..fa7fabfaef6 100644
--- a/spec/frontend/vue_shared/components/registry/registry_search_spec.js
+++ b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
@@ -108,7 +108,7 @@ describe('Registry Search', () => {
]);
});
- it('on sort item click emits sorting:changed event ', () => {
+ it('on sort item click emits sorting:changed event', () => {
mountComponent();
findSortingItems().at(1).vm.$emit('click');
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
index afad9314ace..48530a0261f 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
@@ -56,7 +56,7 @@ export const mockSuggestedColors = {
'#013220': 'Dark green',
'#6699cc': 'Blue-gray',
'#0000ff': 'Blue',
- '#e6e6fa': 'Lavendar',
+ '#e6e6fa': 'Lavender',
'#9400d3': 'Dark violet',
'#330066': 'Deep violet',
'#808080': 'Gray',
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index 4fbc907a813..e020d9a557e 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -110,6 +110,13 @@ describe('Source Viewer component', () => {
expect(hljs.registerLanguage).toHaveBeenCalledWith('json', languageDefinition.default);
});
+ it('correctly maps languages starting with uppercase', async () => {
+ await createComponent({ language: 'Python3' });
+ const languageDefinition = await import(`highlight.js/lib/languages/python`);
+
+ expect(hljs.registerLanguage).toHaveBeenCalledWith('python', languageDefinition.default);
+ });
+
it('highlights the first chunk', () => {
expect(hljs.highlight).toHaveBeenCalledWith(chunk1.trim(), { language: mappedLanguage });
});
@@ -149,7 +156,7 @@ describe('Source Viewer component', () => {
it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
findChunks().at(0).vm.$emit('appear');
- expect(eventHub.$emit).toBeCalledWith('showBlobInteractionZones', path);
+ expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
});
describe('LineHighlighter', () => {
diff --git a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
index 1798ca5ccde..f9d615d4f68 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
+++ b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
@@ -5,7 +5,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
@@ -86,7 +86,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
@@ -171,7 +171,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
@@ -256,7 +256,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
@@ -342,7 +342,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
@@ -428,7 +428,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
@@ -514,7 +514,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
class="gl-w-full gl-relative"
>
<button
- class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4 gl-mb-0"
type="button"
>
<div
diff --git a/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js b/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
index 70dec42ab32..521744154ba 100644
--- a/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
+++ b/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
@@ -84,7 +84,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', () => {
- expect(defaultScopedSlotSpy).lastCalledWith(initialSlotProps());
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(initialSlotProps());
});
});
@@ -98,7 +98,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isDismissed: true,
isLoadingQuery: false,
@@ -117,7 +117,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isLoadingQuery: false,
shouldShowCallout: true,
@@ -136,7 +136,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isLoadingQuery: false,
queryError: expect.any(Error),
@@ -155,7 +155,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isAnonUser: true,
isLoadingQuery: false,
@@ -186,7 +186,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isLoadingQuery: false,
shouldShowCallout: true,
@@ -217,7 +217,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', async () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isLoadingQuery: false,
shouldShowCallout: true,
@@ -229,7 +229,7 @@ describe('UserCalloutDismisser', () => {
// Wait for Vue re-render due to prop change
await nextTick();
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isDismissed: true,
isLoadingMutation: true,
@@ -240,7 +240,7 @@ describe('UserCalloutDismisser', () => {
// Wait for mutation to resolve
await waitForPromises();
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isDismissed: true,
isLoadingQuery: false,
@@ -270,7 +270,7 @@ describe('UserCalloutDismisser', () => {
});
it('passes expected slot props to child', async () => {
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isLoadingQuery: false,
shouldShowCallout: true,
@@ -282,7 +282,7 @@ describe('UserCalloutDismisser', () => {
// Wait for Vue re-render due to prop change
await nextTick();
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isDismissed: true,
isLoadingMutation: true,
@@ -293,7 +293,7 @@ describe('UserCalloutDismisser', () => {
// Wait for mutation to resolve
await waitForPromises();
- expect(defaultScopedSlotSpy).lastCalledWith(
+ expect(defaultScopedSlotSpy).toHaveBeenLastCalledWith(
initialSlotProps({
isDismissed: true,
isLoadingQuery: false,
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 b7ce3e47cef..6d48000beb0 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,15 @@
import { GlSkeletonLoader, GlIcon } from '@gitlab/ui';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { sprintf } from '~/locale';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
+import {
+ I18N_USER_BLOCKED,
+ I18N_USER_LEARN,
+ I18N_USER_FOLLOW,
+ I18N_USER_UNFOLLOW,
+} from '~/vue_shared/components/user_popover/constants';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { followUser, unfollowUser } from '~/api/user_api';
@@ -310,7 +317,9 @@ describe('User Popover Component', () => {
const securityBotDocsLink = findSecurityBotDocsLink();
expect(securityBotDocsLink.exists()).toBe(true);
expect(securityBotDocsLink.attributes('href')).toBe(SECURITY_BOT_USER.websiteUrl);
- expect(securityBotDocsLink.text()).toBe('Learn more about GitLab Security Bot');
+ expect(securityBotDocsLink.text()).toBe(
+ sprintf(I18N_USER_LEARN, { name: SECURITY_BOT_USER.name }),
+ );
});
it("does not show a link to the bot's documentation if there is no website_url", () => {
@@ -320,9 +329,10 @@ describe('User Popover Component', () => {
});
it("doesn't escape user's name", () => {
- createWrapper({ user: { ...SECURITY_BOT_USER, name: '%<>\';"' } });
+ const name = '%<>\';"';
+ createWrapper({ user: { ...SECURITY_BOT_USER, name } });
const securityBotDocsLink = findSecurityBotDocsLink();
- expect(securityBotDocsLink.text()).toBe('Learn more about %<>\';"');
+ expect(securityBotDocsLink.text()).toBe(sprintf(I18N_USER_LEARN, { name }, false));
});
it('does not display local time', () => {
@@ -336,7 +346,7 @@ describe('User Popover Component', () => {
beforeEach(() => createWrapper());
it('renders the Follow button with the correct variant', () => {
- expect(findToggleFollowButton().text()).toBe('Follow');
+ expect(findToggleFollowButton().text()).toBe(I18N_USER_FOLLOW);
expect(findToggleFollowButton().props('variant')).toBe('confirm');
});
@@ -387,7 +397,7 @@ describe('User Popover Component', () => {
beforeEach(() => createWrapper({ user: { ...DEFAULT_PROPS.user, isFollowed: true } }));
it('renders the Unfollow button with the correct variant', () => {
- expect(findToggleFollowButton().text()).toBe('Unfollow');
+ expect(findToggleFollowButton().text()).toBe(I18N_USER_UNFOLLOW);
expect(findToggleFollowButton().props('variant')).toBe('default');
});
@@ -441,6 +451,25 @@ describe('User Popover Component', () => {
});
});
+ describe('when the user is blocked', () => {
+ const bio = 'My super interesting bio';
+ const status = 'My status';
+ beforeEach(() =>
+ createWrapper({
+ user: { ...DEFAULT_PROPS.user, state: 'blocked', bio, status: { message_html: status } },
+ }),
+ );
+
+ it('renders warning', () => {
+ expect(wrapper.text()).toContain(I18N_USER_BLOCKED);
+ });
+
+ it("doesn't show other information", () => {
+ expect(wrapper.text()).not.toContain(bio);
+ expect(wrapper.text()).not.toContain(status);
+ });
+ });
+
describe('when API does not support `isFollowed`', () => {
beforeEach(() => {
const user = {
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
index d843da4da5b..e5594b6d37e 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
@@ -164,8 +164,7 @@ describe('IssuableEditForm', () => {
const titleInputEl = wrapper.findComponent(GlFormInput);
titleInputEl.vm.$emit('keydown', eventObj, 'title');
-
- expect(wrapper.emitted('keydown-title')).toBeTruthy();
+ expect(wrapper.emitted('keydown-title')).toHaveLength(1);
expect(wrapper.emitted('keydown-title')[0]).toMatchObject([
eventObj,
{
@@ -179,8 +178,7 @@ describe('IssuableEditForm', () => {
const descriptionInputEl = wrapper.find('[data-testid="description"] textarea');
descriptionInputEl.trigger('keydown', eventObj, 'description');
-
- expect(wrapper.emitted('keydown-description')).toBeTruthy();
+ expect(wrapper.emitted('keydown-description')).toHaveLength(1);
expect(wrapper.emitted('keydown-description')[0]).toMatchObject([
eventObj,
{
diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
index 39909e26ef0..0a5e46d9263 100644
--- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
+++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
@@ -93,7 +93,7 @@ describe('ManageViaMr component', () => {
createComponent({ apolloProvider, featureName, featureType, isFeatureConfigured: true });
});
- it('it does not render a button', () => {
+ it('does not render a button', () => {
expect(findButton().exists()).toBe(false);
});
});
@@ -104,7 +104,7 @@ describe('ManageViaMr component', () => {
createComponent({ apolloProvider, featureName, featureType, isFeatureConfigured: false });
});
- it('it does render a button', () => {
+ it('does render a button', () => {
expect(findButton().exists()).toBe(true);
});
diff --git a/spec/frontend/work_items/components/item_title_spec.js b/spec/frontend/work_items/components/item_title_spec.js
index de20369eb1b..13e04ef6671 100644
--- a/spec/frontend/work_items/components/item_title_spec.js
+++ b/spec/frontend/work_items/components/item_title_spec.js
@@ -49,6 +49,6 @@ describe('ItemTitle', () => {
findInputEl().element.innerText = mockUpdatedTitle;
await findInputEl().trigger(sourceEvent);
- expect(wrapper.emitted(eventName)).toBeTruthy();
+ expect(wrapper.emitted(eventName)).toBeDefined();
});
});
diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js
index a1f1d47ab90..3c312fb4552 100644
--- a/spec/frontend/work_items/components/work_item_actions_spec.js
+++ b/spec/frontend/work_items/components/work_item_actions_spec.js
@@ -1,15 +1,30 @@
-import { GlModal } from '@gitlab/ui';
+import { GlDropdownDivider, GlModal } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import WorkItemActions from '~/work_items/components/work_item_actions.vue';
+const TEST_ID_CONFIDENTIALITY_TOGGLE_ACTION = 'confidentiality-toggle-action';
+const TEST_ID_DELETE_ACTION = 'delete-action';
+
describe('WorkItemActions component', () => {
let wrapper;
let glModalDirective;
const findModal = () => wrapper.findComponent(GlModal);
const findConfidentialityToggleButton = () =>
- wrapper.findByTestId('confidentiality-toggle-action');
- const findDeleteButton = () => wrapper.findByTestId('delete-action');
+ wrapper.findByTestId(TEST_ID_CONFIDENTIALITY_TOGGLE_ACTION);
+ const findDeleteButton = () => wrapper.findByTestId(TEST_ID_DELETE_ACTION);
+ const findDropdownItems = () => wrapper.findAll('[data-testid="work-item-actions-dropdown"] > *');
+ const findDropdownItemsActual = () =>
+ findDropdownItems().wrappers.map((x) => {
+ if (x.is(GlDropdownDivider)) {
+ return { divider: true };
+ }
+
+ return {
+ testId: x.attributes('data-testid'),
+ text: x.text(),
+ };
+ });
const createComponent = ({
canUpdate = true,
@@ -19,7 +34,14 @@ describe('WorkItemActions component', () => {
} = {}) => {
glModalDirective = jest.fn();
wrapper = shallowMountExtended(WorkItemActions, {
- propsData: { workItemId: '123', canUpdate, canDelete, isConfidential, isParentConfidential },
+ propsData: {
+ workItemId: '123',
+ canUpdate,
+ canDelete,
+ isConfidential,
+ isParentConfidential,
+ workItemType: 'Task',
+ },
directives: {
glModal: {
bind(_, { value }) {
@@ -44,8 +66,19 @@ describe('WorkItemActions component', () => {
it('renders dropdown actions', () => {
createComponent();
- expect(findConfidentialityToggleButton().exists()).toBe(true);
- expect(findDeleteButton().exists()).toBe(true);
+ expect(findDropdownItemsActual()).toEqual([
+ {
+ testId: TEST_ID_CONFIDENTIALITY_TOGGLE_ACTION,
+ text: 'Turn on confidentiality',
+ },
+ {
+ divider: true,
+ },
+ {
+ testId: TEST_ID_DELETE_ACTION,
+ text: 'Delete task',
+ },
+ ]);
});
describe('toggle confidentiality action', () => {
@@ -103,7 +136,8 @@ describe('WorkItemActions component', () => {
canDelete: false,
});
- expect(wrapper.findByTestId('delete-action').exists()).toBe(false);
+ expect(findDeleteButton().exists()).toBe(false);
+ expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index f0ef8aee7a9..28231fad108 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -1,4 +1,4 @@
-import { GlLink, GlTokenSelector, GlSkeletonLoader } from '@gitlab/ui';
+import { GlLink, GlTokenSelector, GlSkeletonLoader, GlIntersectionObserver } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -8,12 +8,17 @@ import { mockTracking } from 'helpers/tracking_helper';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
+import { temporaryConfig } from '~/graphql_shared/issuable_client';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
-import { i18n, TASK_TYPE_NAME, TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
-import { temporaryConfig } from '~/work_items/graphql/provider';
+import {
+ i18n,
+ TASK_TYPE_NAME,
+ TRACKING_CATEGORY_SHOW,
+ DEFAULT_PAGE_SIZE_ASSIGNEES,
+} from '~/work_items/constants';
import {
projectMembersResponseWithCurrentUser,
mockAssignees,
@@ -22,6 +27,8 @@ import {
currentUserNullResponse,
projectMembersResponseWithoutCurrentUser,
updateWorkItemMutationResponse,
+ projectMembersResponseWithCurrentUserWithNextPage,
+ projectMembersResponseWithNoMatchingUsers,
} from '../mock_data';
Vue.use(VueApollo);
@@ -40,15 +47,25 @@ describe('WorkItemAssignees component', () => {
const findEmptyState = () => wrapper.findByTestId('empty-state');
const findAssignSelfButton = () => wrapper.findByTestId('assign-self');
const findAssigneesTitle = () => wrapper.findByTestId('assignees-title');
+ const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
+
+ const triggerInfiniteScroll = () =>
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
const successSearchQueryHandler = jest
.fn()
.mockResolvedValue(projectMembersResponseWithCurrentUser);
+ const successSearchQueryHandlerWithMoreAssignees = jest
+ .fn()
+ .mockResolvedValue(projectMembersResponseWithCurrentUserWithNextPage);
const successCurrentUserQueryHandler = jest.fn().mockResolvedValue(currentUserResponse);
const noCurrentUserQueryHandler = jest.fn().mockResolvedValue(currentUserNullResponse);
const successUpdateWorkItemMutationHandler = jest
.fn()
.mockResolvedValue(updateWorkItemMutationResponse);
+ const successSearchWithNoMatchingUsers = jest
+ .fn()
+ .mockResolvedValue(projectMembersResponseWithNoMatchingUsers);
const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
@@ -82,9 +99,6 @@ describe('WorkItemAssignees component', () => {
});
wrapper = mountExtended(WorkItemAssignees, {
- provide: {
- fullPath: 'test-project-path',
- },
propsData: {
assignees,
workItemId,
@@ -92,6 +106,7 @@ describe('WorkItemAssignees component', () => {
workItemType: TASK_TYPE_NAME,
canUpdate,
canInviteMembers,
+ fullPath: 'test-project-path',
},
attachTo: document.body,
apolloProvider,
@@ -459,4 +474,56 @@ describe('WorkItemAssignees component', () => {
expect(findInviteMembersTrigger().exists()).toBe(true);
});
});
+
+ describe('load more assignees', () => {
+ it('does not have intersection observer when no matching users', async () => {
+ createComponent({ searchQueryHandler: successSearchWithNoMatchingUsers });
+ findTokenSelector().vm.$emit('focus');
+ await nextTick();
+
+ expect(findSkeletonLoader().exists()).toBe(true);
+
+ await waitForPromises();
+
+ expect(findSkeletonLoader().exists()).toBe(false);
+ expect(findIntersectionObserver().exists()).toBe(false);
+ });
+
+ it('does not trigger load more when does not have next page', async () => {
+ createComponent();
+ findTokenSelector().vm.$emit('focus');
+ await nextTick();
+
+ expect(findSkeletonLoader().exists()).toBe(true);
+
+ await waitForPromises();
+
+ expect(findSkeletonLoader().exists()).toBe(false);
+
+ expect(findIntersectionObserver().exists()).toBe(false);
+ });
+
+ it('triggers load more when there are more users', async () => {
+ createComponent({ searchQueryHandler: successSearchQueryHandlerWithMoreAssignees });
+ findTokenSelector().vm.$emit('focus');
+ await nextTick();
+
+ expect(findSkeletonLoader().exists()).toBe(true);
+
+ await waitForPromises();
+
+ expect(findSkeletonLoader().exists()).toBe(false);
+ expect(findIntersectionObserver().exists()).toBe(true);
+
+ triggerInfiniteScroll();
+
+ expect(successSearchQueryHandlerWithMoreAssignees).toHaveBeenCalledWith({
+ first: DEFAULT_PAGE_SIZE_ASSIGNEES,
+ after:
+ projectMembersResponseWithCurrentUserWithNextPage.data.workspace.users.pageInfo.endCursor,
+ search: '',
+ fullPath: 'test-project-path',
+ });
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index 8017c46dea8..d3165d8dc26 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -10,9 +10,9 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
-import updateWorkItemWidgetsMutation from '~/work_items/graphql/update_work_item_widgets.mutation.graphql';
+import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import {
- updateWorkItemWidgetsResponse,
+ updateWorkItemMutationResponse,
workItemResponseFactory,
workItemQueryResponse,
} from '../mock_data';
@@ -31,7 +31,7 @@ describe('WorkItemDescription', () => {
Vue.use(VueApollo);
- const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemWidgetsResponse);
+ const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
const findEditButton = () => wrapper.find('[data-testid="edit-description"]');
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
@@ -53,13 +53,11 @@ describe('WorkItemDescription', () => {
wrapper = shallowMount(WorkItemDescription, {
apolloProvider: createMockApollo([
[workItemQuery, workItemResponseHandler],
- [updateWorkItemWidgetsMutation, mutationHandler],
+ [updateWorkItemMutation, mutationHandler],
]),
propsData: {
workItemId: id,
- },
- provide: {
- fullPath: '/group/project',
+ fullPath: 'test-project-path',
},
stubs: {
MarkdownField,
@@ -175,7 +173,7 @@ describe('WorkItemDescription', () => {
isEditing: true,
mutationHandler: jest.fn().mockResolvedValue({
data: {
- workItemUpdateWidgets: {
+ workItemUpdate: {
workItem: {},
errors: [error],
},
diff --git a/spec/frontend/work_items/components/work_item_detail_modal_spec.js b/spec/frontend/work_items/components/work_item_detail_modal_spec.js
index 01891012f99..6b1ef8971d3 100644
--- a/spec/frontend/work_items/components/work_item_detail_modal_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_modal_spec.js
@@ -113,7 +113,7 @@ describe('WorkItemDetailModal component', () => {
createComponent();
findModal().vm.$emit('hide');
- expect(wrapper.emitted('close')).toBeTruthy();
+ expect(wrapper.emitted('close')).toHaveLength(1);
});
it('hides the modal when WorkItemDetail emits `close` event', () => {
diff --git a/spec/frontend/work_items/pages/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index 823981df880..b047e0dc8d7 100644
--- a/spec/frontend/work_items/pages/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -2,29 +2,33 @@ import { GlAlert, GlBadge, GlLoadingIcon, GlSkeletonLoader, GlButton } from '@gi
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
+import workItemWeightSubscription from 'ee_component/work_items/graphql/work_item_weight.subscription.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
import WorkItemActions from '~/work_items/components/work_item_actions.vue';
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
+import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue';
import WorkItemState from '~/work_items/components/work_item_state.vue';
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
-import WorkItemWeight from '~/work_items/components/work_item_weight.vue';
import WorkItemInformation from '~/work_items/components/work_item_information.vue';
import { i18n } from '~/work_items/constants';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+import workItemDatesSubscription from '~/work_items/graphql/work_item_dates.subscription.graphql';
import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
-import { temporaryConfig } from '~/work_items/graphql/provider';
+import { temporaryConfig } from '~/graphql_shared/issuable_client';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import {
- workItemTitleSubscriptionResponse,
- workItemResponseFactory,
mockParent,
+ workItemDatesSubscriptionResponse,
+ workItemResponseFactory,
+ workItemTitleSubscriptionResponse,
+ workItemWeightSubscriptionResponse,
} from '../mock_data';
describe('WorkItemDetail component', () => {
@@ -40,7 +44,9 @@ describe('WorkItemDetail component', () => {
canDelete: true,
});
const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
- const initialSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
+ const datesSubscriptionHandler = jest.fn().mockResolvedValue(workItemDatesSubscriptionResponse);
+ const titleSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
+ const weightSubscriptionHandler = jest.fn().mockResolvedValue(workItemWeightSubscriptionResponse);
const findAlert = () => wrapper.findComponent(GlAlert);
const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader);
@@ -49,9 +55,9 @@ describe('WorkItemDetail component', () => {
const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle);
const findWorkItemState = () => wrapper.findComponent(WorkItemState);
const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription);
+ const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate);
const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees);
const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels);
- const findWorkItemWeight = () => wrapper.findComponent(WorkItemWeight);
const findParent = () => wrapper.find('[data-testid="work-item-parent"]');
const findParentButton = () => findParent().findComponent(GlButton);
const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
@@ -64,19 +70,26 @@ describe('WorkItemDetail component', () => {
updateInProgress = false,
workItemId = workItemQueryResponse.data.workItem.id,
handler = successHandler,
- subscriptionHandler = initialSubscriptionHandler,
+ subscriptionHandler = titleSubscriptionHandler,
confidentialityMock = [updateWorkItemMutation, jest.fn()],
workItemsMvc2Enabled = false,
includeWidgets = false,
error = undefined,
} = {}) => {
+ const handlers = [
+ [workItemQuery, handler],
+ [workItemTitleSubscription, subscriptionHandler],
+ [workItemDatesSubscription, datesSubscriptionHandler],
+ confidentialityMock,
+ ];
+
+ if (IS_EE) {
+ handlers.push([workItemWeightSubscription, weightSubscriptionHandler]);
+ }
+
wrapper = shallowMount(WorkItemDetail, {
apolloProvider: createMockApollo(
- [
- [workItemQuery, handler],
- [workItemTitleSubscription, subscriptionHandler],
- confidentialityMock,
- ],
+ handlers,
{},
{
typePolicies: includeWidgets ? temporaryConfig.cacheConfig.typePolicies : {},
@@ -93,6 +106,7 @@ describe('WorkItemDetail component', () => {
glFeatures: {
workItemsMvc2: workItemsMvc2Enabled,
},
+ hasIssueWeightsFeature: true,
},
});
};
@@ -134,6 +148,10 @@ describe('WorkItemDetail component', () => {
expect(findWorkItemState().exists()).toBe(true);
expect(findWorkItemTitle().exists()).toBe(true);
});
+
+ it('updates the document title', () => {
+ expect(document.title).toEqual('Updated title · Task · test-project-path');
+ });
});
describe('close button', () => {
@@ -295,8 +313,7 @@ describe('WorkItemDetail component', () => {
await waitForPromises();
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
await waitForPromises();
-
- expect(wrapper.emitted('workItemUpdated')).toBeFalsy();
+ expect(wrapper.emitted('workItemUpdated')).toBeUndefined();
await nextTick();
@@ -379,23 +396,50 @@ describe('WorkItemDetail component', () => {
it('shows an error message when WorkItemTitle emits an `error` event', async () => {
createComponent();
await waitForPromises();
+ const updateError = 'Failed to update';
- findWorkItemTitle().vm.$emit('error', i18n.updateError);
+ findWorkItemTitle().vm.$emit('error', updateError);
await waitForPromises();
- expect(findAlert().text()).toBe(i18n.updateError);
+ expect(findAlert().text()).toBe(updateError);
});
- it('calls the subscription', () => {
- createComponent();
+ describe('subscriptions', () => {
+ it('calls the title subscription', () => {
+ createComponent();
+
+ expect(titleSubscriptionHandler).toHaveBeenCalledWith({
+ issuableId: workItemQueryResponse.data.workItem.id,
+ });
+ });
- expect(initialSubscriptionHandler).toHaveBeenCalledWith({
- issuableId: workItemQueryResponse.data.workItem.id,
+ describe('dates subscription', () => {
+ describe('when the due date widget exists', () => {
+ it('calls the dates subscription', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(datesSubscriptionHandler).toHaveBeenCalledWith({
+ issuableId: workItemQueryResponse.data.workItem.id,
+ });
+ });
+ });
+
+ describe('when the due date widget does not exist', () => {
+ it('does not call the dates subscription', async () => {
+ const response = workItemResponseFactory({ datesWidgetPresent: false });
+ const handler = jest.fn().mockResolvedValue(response);
+ createComponent({ handler, workItemsMvc2Enabled: true });
+ await waitForPromises();
+
+ expect(datesSubscriptionHandler).not.toHaveBeenCalled();
+ });
+ });
});
});
- describe('when work_items_mvc_2 feature flag is enabled', () => {
- it('renders assignees component when assignees widget is returned from the API', async () => {
+ describe('assignees widget', () => {
+ it('renders assignees component when widget is returned from the API', async () => {
createComponent({
workItemsMvc2Enabled: true,
});
@@ -404,7 +448,7 @@ describe('WorkItemDetail component', () => {
expect(findWorkItemAssignees().exists()).toBe(true);
});
- it('does not render assignees component when assignees widget is not returned from the API', async () => {
+ it('does not render assignees component when widget is not returned from the API', async () => {
createComponent({
workItemsMvc2Enabled: true,
handler: jest
@@ -417,13 +461,6 @@ describe('WorkItemDetail component', () => {
});
});
- it('does not render assignees component when assignees feature flag is disabled', async () => {
- createComponent();
- await waitForPromises();
-
- expect(findWorkItemAssignees().exists()).toBe(false);
- });
-
describe('labels widget', () => {
it.each`
description | includeWidgets | exists
@@ -437,30 +474,31 @@ describe('WorkItemDetail component', () => {
});
});
- describe('weight widget', () => {
+ describe('dates widget', () => {
describe.each`
- description | weightWidgetPresent | exists
- ${'when widget is returned from API'} | ${true} | ${true}
- ${'when widget is not returned from API'} | ${false} | ${false}
- `('$description', ({ weightWidgetPresent, exists }) => {
- it(`${weightWidgetPresent ? 'renders' : 'does not render'} weight component`, async () => {
- const response = workItemResponseFactory({ weightWidgetPresent });
+ description | datesWidgetPresent | exists
+ ${'when widget is returned from API'} | ${true} | ${true}
+ ${'when widget is not returned from API'} | ${false} | ${false}
+ `('$description', ({ datesWidgetPresent, exists }) => {
+ it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, async () => {
+ const response = workItemResponseFactory({ datesWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
- createComponent({ handler });
+ createComponent({ handler, workItemsMvc2Enabled: true });
await waitForPromises();
- expect(findWorkItemWeight().exists()).toBe(exists);
+ expect(findWorkItemDueDate().exists()).toBe(exists);
});
});
it('shows an error message when it emits an `error` event', async () => {
createComponent({ workItemsMvc2Enabled: true });
await waitForPromises();
+ const updateError = 'Failed to update';
- findWorkItemWeight().vm.$emit('error', i18n.updateError);
+ findWorkItemDueDate().vm.$emit('error', updateError);
await waitForPromises();
- expect(findAlert().text()).toBe(i18n.updateError);
+ expect(findAlert().text()).toBe(updateError);
});
});
diff --git a/spec/frontend/work_items/components/work_item_due_date_spec.js b/spec/frontend/work_items/components/work_item_due_date_spec.js
new file mode 100644
index 00000000000..1d76154a1f0
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_due_date_spec.js
@@ -0,0 +1,346 @@
+import { GlFormGroup, GlDatepicker } from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue';
+import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
+import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import { updateWorkItemMutationResponse, updateWorkItemMutationErrorResponse } from '../mock_data';
+
+describe('WorkItemDueDate component', () => {
+ let wrapper;
+
+ Vue.use(VueApollo);
+
+ const workItemId = 'gid://gitlab/WorkItem/1';
+ const updateWorkItemMutationHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
+
+ const findStartDateButton = () =>
+ wrapper.findByRole('button', { name: WorkItemDueDate.i18n.addStartDate });
+ const findStartDateInput = () => wrapper.findByLabelText(WorkItemDueDate.i18n.startDate);
+ const findStartDatePicker = () => wrapper.findComponent(GlDatepicker);
+ const findDueDateButton = () =>
+ wrapper.findByRole('button', { name: WorkItemDueDate.i18n.addDueDate });
+ const findDueDateInput = () => wrapper.findByLabelText(WorkItemDueDate.i18n.dueDate);
+ const findDueDatePicker = () => wrapper.findAllComponents(GlDatepicker).at(1);
+ const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
+
+ const createComponent = ({
+ canUpdate = false,
+ dueDate = null,
+ startDate = null,
+ mutationHandler = updateWorkItemMutationHandler,
+ } = {}) => {
+ wrapper = mountExtended(WorkItemDueDate, {
+ apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]),
+ propsData: {
+ canUpdate,
+ dueDate,
+ startDate,
+ workItemId,
+ workItemType: 'Task',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when can update', () => {
+ describe('start date', () => {
+ describe('`Add start date` button', () => {
+ describe.each`
+ description | startDate | exists
+ ${'when there is no start date'} | ${null} | ${true}
+ ${'when there is a start date'} | ${'2022-01-01'} | ${false}
+ `('$description', ({ startDate, exists }) => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, startDate });
+ });
+
+ it(exists ? 'renders' : 'does not render', () => {
+ expect(findStartDateButton().exists()).toBe(exists);
+ });
+ });
+
+ describe('when it emits `click` event', () => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, startDate: null });
+ findStartDateButton().vm.$emit('click');
+ });
+
+ it('renders start date picker', () => {
+ expect(findStartDateInput().exists()).toBe(true);
+ });
+
+ it('hides itself', () => {
+ expect(findStartDateButton().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('date picker', () => {
+ describe('when it emits a `clear` event', () => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-01-01', startDate: '2022-01-01' });
+ findStartDatePicker().vm.$emit('clear');
+ });
+
+ it('hides the date picker', () => {
+ expect(findStartDateInput().exists()).toBe(false);
+ });
+
+ it('shows the `Add start date` button', () => {
+ expect(findStartDateButton().exists()).toBe(true);
+ });
+
+ it('calls a mutation to update the dates', () => {
+ expect(updateWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: workItemId,
+ startAndDueDateWidget: {
+ dueDate: new Date('2022-01-01T00:00:00.000Z'),
+ startDate: null,
+ },
+ },
+ });
+ });
+ });
+
+ describe('when it emits a `close` event', () => {
+ describe('when the start date is earlier than the due date', () => {
+ const startDate = new Date('2022-01-01T00:00:00.000Z');
+
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-12-31', startDate: '2022-12-31' });
+ findStartDatePicker().vm.$emit('input', startDate);
+ findStartDatePicker().vm.$emit('close');
+ });
+
+ it('calls a mutation to update the dates', () => {
+ expect(updateWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: workItemId,
+ startAndDueDateWidget: {
+ dueDate: new Date('2022-12-31T00:00:00.000Z'),
+ startDate,
+ },
+ },
+ });
+ });
+ });
+
+ describe('when the start date is later than the due date', () => {
+ const startDate = new Date('2030-01-01T00:00:00.000Z');
+ let datePickerOpenSpy;
+
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-12-31', startDate: '2022-12-31' });
+ datePickerOpenSpy = jest.spyOn(wrapper.vm.$refs.dueDatePicker.calendar, 'show');
+ findStartDatePicker().vm.$emit('input', startDate);
+ findStartDatePicker().vm.$emit('close');
+ });
+
+ it('does not call a mutation to update the dates', () => {
+ expect(updateWorkItemMutationHandler).not.toHaveBeenCalled();
+ });
+
+ it('updates the due date picker to the same date', () => {
+ expect(findDueDatePicker().props('value')).toEqual(startDate);
+ });
+
+ it('opens the due date picker', () => {
+ expect(datePickerOpenSpy).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+ });
+
+ describe('due date', () => {
+ describe('`Add due date` button', () => {
+ describe.each`
+ description | dueDate | exists
+ ${'when there is no due date'} | ${null} | ${true}
+ ${'when there is a due date'} | ${'2022-01-01'} | ${false}
+ `('$description', ({ dueDate, exists }) => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate });
+ });
+
+ it(exists ? 'renders' : 'does not render', () => {
+ expect(findDueDateButton().exists()).toBe(exists);
+ });
+ });
+
+ describe('when it emits `click` event', () => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: null });
+ findDueDateButton().vm.$emit('click');
+ });
+
+ it('renders due date picker', () => {
+ expect(findDueDateInput().exists()).toBe(true);
+ });
+
+ it('hides itself', () => {
+ expect(findDueDateButton().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('date picker', () => {
+ describe('when it emits a `clear` event', () => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-01-01', startDate: '2022-01-01' });
+ findDueDatePicker().vm.$emit('clear');
+ });
+
+ it('hides the date picker', () => {
+ expect(findDueDateInput().exists()).toBe(false);
+ });
+
+ it('shows the `Add due date` button', () => {
+ expect(findDueDateButton().exists()).toBe(true);
+ });
+
+ it('calls a mutation to update the dates', () => {
+ expect(updateWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: workItemId,
+ startAndDueDateWidget: {
+ dueDate: null,
+ startDate: new Date('2022-01-01T00:00:00.000Z'),
+ },
+ },
+ });
+ });
+ });
+
+ describe('when it emits a `close` event', () => {
+ const dueDate = new Date('2022-12-31T00:00:00.000Z');
+
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-01-01', startDate: '2022-01-01' });
+ findDueDatePicker().vm.$emit('input', dueDate);
+ findDueDatePicker().vm.$emit('close');
+ });
+
+ it('calls a mutation to update the dates', () => {
+ expect(updateWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: workItemId,
+ startAndDueDateWidget: {
+ dueDate,
+ startDate: new Date('2022-01-01T00:00:00.000Z'),
+ },
+ },
+ });
+ });
+ });
+ });
+ });
+
+ describe('when updating date', () => {
+ describe('when dates are changed', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-12-31', startDate: '2022-12-31' });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ findStartDatePicker().vm.$emit('input', new Date('2022-01-01T00:00:00.000Z'));
+ findStartDatePicker().vm.$emit('close');
+ });
+
+ it('mutation is called to update dates', () => {
+ expect(updateWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: workItemId,
+ startAndDueDateWidget: {
+ dueDate: new Date('2022-12-31T00:00:00.000Z'),
+ startDate: new Date('2022-01-01T00:00:00.000Z'),
+ },
+ },
+ });
+ });
+
+ it('start date input is disabled', () => {
+ expect(findStartDatePicker().props('disabled')).toBe(true);
+ });
+
+ it('due date input is disabled', () => {
+ expect(findDueDatePicker().props('disabled')).toBe(true);
+ });
+
+ it('tracks updating the dates', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_dates', {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'item_dates',
+ property: 'type_Task',
+ });
+ });
+ });
+
+ describe('when dates are unchanged', () => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true, dueDate: '2022-12-31', startDate: '2022-12-31' });
+
+ findStartDatePicker().vm.$emit('input', new Date('2022-12-31T00:00:00.000Z'));
+ findStartDatePicker().vm.$emit('close');
+ });
+
+ it('mutation is not called to update dates', () => {
+ expect(updateWorkItemMutationHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe.each`
+ description | mutationHandler
+ ${'when there is a GraphQL error'} | ${jest.fn().mockResolvedValue(updateWorkItemMutationErrorResponse)}
+ ${'when there is a network error'} | ${jest.fn().mockRejectedValue(new Error())}
+ `('$description', ({ mutationHandler }) => {
+ beforeEach(() => {
+ createComponent({
+ canUpdate: true,
+ dueDate: '2022-12-31',
+ startDate: '2022-12-31',
+ mutationHandler,
+ });
+
+ findStartDatePicker().vm.$emit('input', new Date('2022-01-01T00:00:00.000Z'));
+ findStartDatePicker().vm.$emit('close');
+ return waitForPromises();
+ });
+
+ it('emits an error', () => {
+ expect(wrapper.emitted('error')).toEqual([
+ ['Something went wrong while updating the task. Please try again.'],
+ ]);
+ });
+ });
+ });
+ });
+
+ describe('when cannot update', () => {
+ it('start and due date inputs are disabled', async () => {
+ createComponent({ canUpdate: false, dueDate: '2022-01-01', startDate: '2022-01-01' });
+ await nextTick();
+
+ expect(findStartDateInput().props('disabled')).toBe(true);
+ expect(findDueDateInput().props('disabled')).toBe(true);
+ });
+
+ describe('when there is no start and due date', () => {
+ it('shows None', () => {
+ createComponent({ canUpdate: false, dueDate: null, startDate: null });
+
+ expect(findGlFormGroup().text()).toContain(WorkItemDueDate.i18n.none);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_information_spec.js b/spec/frontend/work_items/components/work_item_information_spec.js
index d5f6921c2bc..887c5f615e9 100644
--- a/spec/frontend/work_items/components/work_item_information_spec.js
+++ b/spec/frontend/work_items/components/work_item_information_spec.js
@@ -8,7 +8,6 @@ const createComponent = () => mount(WorkItemInformation);
describe('Work item information alert', () => {
let wrapper;
const tasksHelpPath = helpPagePath('user/tasks');
- const workItemsHelpPath = helpPagePath('development/work_items');
const findAlert = () => wrapper.findComponent(GlAlert);
const findHelpLink = () => wrapper.findComponent(GlLink);
@@ -33,16 +32,12 @@ describe('Work item information alert', () => {
expect(findAlert().props('variant')).toBe('tip');
});
- it('should have the correct text for primary button and link', () => {
+ it('should have the correct text for title', () => {
expect(findAlert().props('title')).toBe(WorkItemInformation.i18n.tasksInformationTitle);
- expect(findAlert().props('primaryButtonText')).toBe(
- WorkItemInformation.i18n.learnTasksButtonText,
- );
- expect(findAlert().props('primaryButtonLink')).toBe(tasksHelpPath);
});
it('should have the correct link to work item link', () => {
expect(findHelpLink().exists()).toBe(true);
- expect(findHelpLink().attributes('href')).toBe(workItemsHelpPath);
+ expect(findHelpLink().attributes('href')).toBe(tasksHelpPath);
});
});
diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js
index 1734b901d1a..1d976897c15 100644
--- a/spec/frontend/work_items/components/work_item_labels_spec.js
+++ b/spec/frontend/work_items/components/work_item_labels_spec.js
@@ -9,7 +9,7 @@ import labelSearchQuery from '~/vue_shared/components/sidebar/labels_select_widg
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
import { i18n } from '~/work_items/constants';
-import { temporaryConfig, resolvers } from '~/work_items/graphql/provider';
+import { temporaryConfig, resolvers } from '~/graphql_shared/issuable_client';
import { projectLabelsResponse, mockLabels, workItemQueryResponse } from '../mock_data';
Vue.use(VueApollo);
@@ -45,13 +45,11 @@ describe('WorkItemLabels component', () => {
});
wrapper = mountExtended(WorkItemLabels, {
- provide: {
- fullPath: 'test-project-path',
- },
propsData: {
labels,
workItemId,
canUpdate,
+ fullPath: 'test-project-path',
},
attachTo: document.body,
apolloProvider,
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
new file mode 100644
index 00000000000..1d5472a0473
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
@@ -0,0 +1,122 @@
+import { GlButton, GlIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue';
+
+import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
+import WorkItemLinksMenu from '~/work_items/components/work_item_links/work_item_links_menu.vue';
+
+import { workItemTask, confidentialWorkItemTask, closedWorkItemTask } from '../../mock_data';
+
+describe('WorkItemLinkChild', () => {
+ const WORK_ITEM_ID = 'gid://gitlab/WorkItem/2';
+ let wrapper;
+
+ const createComponent = ({
+ projectPath = 'gitlab-org/gitlab-test',
+ canUpdate = true,
+ issuableGid = WORK_ITEM_ID,
+ childItem = workItemTask,
+ } = {}) => {
+ wrapper = shallowMountExtended(WorkItemLinkChild, {
+ propsData: {
+ projectPath,
+ canUpdate,
+ issuableGid,
+ childItem,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each`
+ status | childItem | statusIconName | statusIconColorClass | rawTimestamp | tooltipContents
+ ${'open'} | ${workItemTask} | ${'issue-open-m'} | ${'gl-text-green-500'} | ${workItemTask.createdAt} | ${'Created'}
+ ${'closed'} | ${closedWorkItemTask} | ${'issue-close'} | ${'gl-text-blue-500'} | ${closedWorkItemTask.closedAt} | ${'Closed'}
+ `(
+ 'renders item status icon and tooltip when item status is `$status`',
+ ({ childItem, statusIconName, statusIconColorClass, rawTimestamp, tooltipContents }) => {
+ createComponent({ childItem });
+
+ const statusIcon = wrapper.findByTestId('item-status-icon').findComponent(GlIcon);
+ const statusTooltip = wrapper.findComponent(RichTimestampTooltip);
+
+ expect(statusIcon.props('name')).toBe(statusIconName);
+ expect(statusIcon.classes()).toContain(statusIconColorClass);
+ expect(statusTooltip.props('rawTimestamp')).toBe(rawTimestamp);
+ expect(statusTooltip.props('timestampTypeText')).toContain(tooltipContents);
+ },
+ );
+
+ it('renders confidential icon when item is confidential', () => {
+ createComponent({ childItem: confidentialWorkItemTask });
+
+ const confidentialIcon = wrapper.findByTestId('confidential-icon');
+
+ expect(confidentialIcon.props('name')).toBe('eye-slash');
+ expect(confidentialIcon.attributes('title')).toBe('Confidential');
+ });
+
+ describe('item title', () => {
+ let titleEl;
+
+ beforeEach(() => {
+ createComponent();
+
+ titleEl = wrapper.findComponent(GlButton);
+ });
+
+ it('renders item title', () => {
+ expect(titleEl.attributes('href')).toBe('/gitlab-org/gitlab-test/-/work_items/4');
+ expect(titleEl.text()).toBe(workItemTask.title);
+ });
+
+ it.each`
+ action | event | emittedEvent
+ ${'clicking'} | ${'click'} | ${'click'}
+ ${'doing mouseover on'} | ${'mouseover'} | ${'mouseover'}
+ ${'doing mouseout on'} | ${'mouseout'} | ${'mouseout'}
+ `('$action item title emit `$emittedEvent` event', ({ event, emittedEvent }) => {
+ const eventObj = {
+ preventDefault: jest.fn(),
+ };
+ titleEl.vm.$emit(event, eventObj);
+
+ expect(wrapper.emitted(emittedEvent)).toEqual([[workItemTask.id, eventObj]]);
+ });
+ });
+
+ describe('item menu', () => {
+ let itemMenuEl;
+
+ beforeEach(() => {
+ createComponent();
+
+ itemMenuEl = wrapper.findComponent(WorkItemLinksMenu);
+ });
+
+ it('renders work-item-links-menu', () => {
+ expect(itemMenuEl.exists()).toBe(true);
+
+ expect(itemMenuEl.attributes()).toMatchObject({
+ 'work-item-id': workItemTask.id,
+ 'parent-work-item-id': WORK_ITEM_ID,
+ });
+ });
+
+ it('does not render work-item-links-menu when canUpdate is false', () => {
+ createComponent({ canUpdate: false });
+
+ expect(wrapper.findComponent(WorkItemLinksMenu).exists()).toBe(false);
+ });
+
+ it('removeChild event on menu triggers `click-remove-child` event', () => {
+ itemMenuEl.vm.$emit('removeChild');
+
+ expect(wrapper.emitted('remove')).toEqual([[workItemTask.id]]);
+ });
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
index 00f508f1548..876aedff08b 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
@@ -1,12 +1,13 @@
import Vue, { nextTick } from 'vue';
-import { GlButton, GlIcon, GlAlert } from '@gitlab/ui';
+import { GlAlert } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import SidebarEventHub from '~/sidebar/event_hub';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql';
import WorkItemLinks from '~/work_items/components/work_item_links/work_item_links.vue';
+import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import changeWorkItemParentMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import getWorkItemLinksQuery from '~/work_items/graphql/work_item_links.query.graphql';
@@ -20,6 +21,20 @@ import {
Vue.use(VueApollo);
+const issueConfidentialityResponse = (confidential = false) => ({
+ data: {
+ workspace: {
+ id: '1',
+ __typename: 'Project',
+ issuable: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/4',
+ confidential,
+ },
+ },
+ },
+});
+
describe('WorkItemLinks', () => {
let wrapper;
let mockApollo;
@@ -36,18 +51,18 @@ describe('WorkItemLinks', () => {
const childWorkItemQueryHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
- const findChildren = () => wrapper.findAll('[data-testid="links-child"]');
-
const createComponent = async ({
data = {},
fetchHandler = jest.fn().mockResolvedValue(workItemHierarchyResponse),
mutationHandler = mutationChangeParentHandler,
+ confidentialQueryHandler = jest.fn().mockResolvedValue(issueConfidentialityResponse()),
} = {}) => {
mockApollo = createMockApollo(
[
[getWorkItemLinksQuery, fetchHandler],
[changeWorkItemParentMutation, mutationHandler],
[workItemQuery, childWorkItemQueryHandler],
+ [issueConfidentialQuery, confidentialQueryHandler],
],
{},
{ addTypename: true },
@@ -61,6 +76,7 @@ describe('WorkItemLinks', () => {
},
provide: {
projectPath: 'project/path',
+ iid: '1',
},
propsData: { issuableId: 1 },
apolloProvider: mockApollo,
@@ -77,8 +93,9 @@ describe('WorkItemLinks', () => {
const findLinksBody = () => wrapper.findByTestId('links-body');
const findEmptyState = () => wrapper.findByTestId('links-empty');
const findToggleAddFormButton = () => wrapper.findByTestId('toggle-add-form');
+ const findWorkItemLinkChildItems = () => wrapper.findAllComponents(WorkItemLinkChild);
+ const findFirstWorkItemLinkChild = () => findWorkItemLinkChildItems().at(0);
const findAddLinksForm = () => wrapper.findByTestId('add-links-form');
- const findFirstLinksMenu = () => wrapper.findByTestId('links-menu');
const findChildrenCount = () => wrapper.findByTestId('children-count');
beforeEach(async () => {
@@ -132,8 +149,7 @@ describe('WorkItemLinks', () => {
it('renders all hierarchy widget children', () => {
expect(findLinksBody().exists()).toBe(true);
- expect(findChildren()).toHaveLength(4);
- expect(findFirstLinksMenu().exists()).toBe(true);
+ expect(findWorkItemLinkChildItems()).toHaveLength(4);
});
it('shows alert when list loading fails', async () => {
@@ -148,40 +164,12 @@ describe('WorkItemLinks', () => {
expect(findAlert().text()).toBe(errorMessage);
});
- it('renders widget child icon and tooltip', () => {
- expect(findChildren().at(0).findComponent(GlIcon).props('name')).toBe('issue-open-m');
- expect(findChildren().at(1).findComponent(GlIcon).props('name')).toBe('issue-close');
- });
-
- it('renders confidentiality icon when child item is confidential', () => {
- const children = wrapper.findAll('[data-testid="links-child"]');
- const confidentialIcon = children.at(0).find('[data-testid="confidential-icon"]');
-
- expect(confidentialIcon.exists()).toBe(true);
- expect(confidentialIcon.props('name')).toBe('eye-slash');
- });
-
it('displays number if children', () => {
expect(findChildrenCount().exists()).toBe(true);
expect(findChildrenCount().text()).toContain('4');
});
- it('refetches child items when `confidentialityUpdated` event is emitted on SidebarEventhub', async () => {
- const fetchHandler = jest.fn().mockResolvedValue(workItemHierarchyResponse);
- await createComponent({
- fetchHandler,
- });
- await waitForPromises();
-
- SidebarEventHub.$emit('confidentialityUpdated');
- await nextTick();
-
- // First call is done on component mount.
- // Second call is done on confidentialityUpdated event.
- expect(fetchHandler).toHaveBeenCalledTimes(2);
- });
-
describe('when no permission to update', () => {
beforeEach(async () => {
await createComponent({
@@ -194,17 +182,21 @@ describe('WorkItemLinks', () => {
});
it('does not display link menu on children', () => {
- expect(findFirstLinksMenu().exists()).toBe(false);
+ expect(findWorkItemLinkChildItems().at(0).props('canUpdate')).toBe(false);
});
});
describe('remove child', () => {
+ let firstChild;
+
beforeEach(async () => {
await createComponent({ mutationHandler: mutationChangeParentHandler });
+
+ firstChild = findFirstWorkItemLinkChild();
});
it('calls correct mutation with correct variables', async () => {
- findFirstLinksMenu().vm.$emit('removeChild');
+ firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
await waitForPromises();
@@ -219,7 +211,7 @@ describe('WorkItemLinks', () => {
});
it('shows toast when mutation succeeds', async () => {
- findFirstLinksMenu().vm.$emit('removeChild');
+ firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
await waitForPromises();
@@ -229,28 +221,30 @@ describe('WorkItemLinks', () => {
});
it('renders correct number of children after removal', async () => {
- expect(findChildren()).toHaveLength(4);
+ expect(findWorkItemLinkChildItems()).toHaveLength(4);
- findFirstLinksMenu().vm.$emit('removeChild');
+ firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
await waitForPromises();
- expect(findChildren()).toHaveLength(3);
+ expect(findWorkItemLinkChildItems()).toHaveLength(3);
});
});
describe('prefetching child items', () => {
+ let firstChild;
+
beforeEach(async () => {
await createComponent();
- });
- const findChildLink = () => findChildren().at(0).findComponent(GlButton);
+ firstChild = findFirstWorkItemLinkChild();
+ });
it('does not fetch the child work item before hovering work item links', () => {
expect(childWorkItemQueryHandler).not.toHaveBeenCalled();
});
it('fetches the child work item if link is hovered for 250+ ms', async () => {
- findChildLink().vm.$emit('mouseover');
+ firstChild.vm.$emit('mouseover', firstChild.vm.childItem.id);
jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
await waitForPromises();
@@ -260,12 +254,24 @@ describe('WorkItemLinks', () => {
});
it('does not fetch the child work item if link is hovered for less than 250 ms', async () => {
- findChildLink().vm.$emit('mouseover');
+ firstChild.vm.$emit('mouseover', firstChild.vm.childItem.id);
jest.advanceTimersByTime(200);
- findChildLink().vm.$emit('mouseout');
+ firstChild.vm.$emit('mouseout', firstChild.vm.childItem.id);
await waitForPromises();
expect(childWorkItemQueryHandler).not.toHaveBeenCalled();
});
});
+
+ describe('when parent item is confidential', () => {
+ it('passes correct confidentiality status to form', async () => {
+ await createComponent({
+ confidentialQueryHandler: jest.fn().mockResolvedValue(issueConfidentialityResponse(true)),
+ });
+ findToggleAddFormButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findAddLinksForm().props('parentConfidential')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_state_spec.js b/spec/frontend/work_items/components/work_item_state_spec.js
index 6b23a6e4795..b24d940d56a 100644
--- a/spec/frontend/work_items/components/work_item_state_spec.js
+++ b/spec/frontend/work_items/components/work_item_state_spec.js
@@ -7,7 +7,6 @@ import waitForPromises from 'helpers/wait_for_promises';
import ItemState from '~/work_items/components/item_state.vue';
import WorkItemState from '~/work_items/components/work_item_state.vue';
import {
- i18n,
STATE_OPEN,
STATE_CLOSED,
STATE_EVENT_CLOSE,
@@ -104,7 +103,9 @@ describe('WorkItemState component', () => {
findItemState().vm.$emit('changed', STATE_CLOSED);
await waitForPromises();
- expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
+ expect(wrapper.emitted('error')).toEqual([
+ ['Something went wrong while updating the task. Please try again.'],
+ ]);
});
it('tracks editing the state', async () => {
diff --git a/spec/frontend/work_items/components/work_item_title_spec.js b/spec/frontend/work_items/components/work_item_title_spec.js
index c0d966abab8..a549aad5cd8 100644
--- a/spec/frontend/work_items/components/work_item_title_spec.js
+++ b/spec/frontend/work_items/components/work_item_title_spec.js
@@ -6,7 +6,7 @@ import { mockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ItemTitle from '~/work_items/components/item_title.vue';
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
-import { i18n, TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
+import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
import { updateWorkItemMutationResponse, workItemQueryResponse } from '../mock_data';
@@ -116,7 +116,9 @@ describe('WorkItemTitle component', () => {
findItemTitle().vm.$emit('title-changed', 'new title');
await waitForPromises();
- expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
+ expect(wrapper.emitted('error')).toEqual([
+ ['Something went wrong while updating the task. Please try again.'],
+ ]);
});
it('tracks editing the title', async () => {
diff --git a/spec/frontend/work_items/components/work_item_type_icon_spec.js b/spec/frontend/work_items/components/work_item_type_icon_spec.js
index 85466578e18..95ddfc3980e 100644
--- a/spec/frontend/work_items/components/work_item_type_icon_spec.js
+++ b/spec/frontend/work_items/components/work_item_type_icon_spec.js
@@ -1,11 +1,17 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
let wrapper;
function createComponent(propsData) {
- wrapper = shallowMount(WorkItemTypeIcon, { propsData });
+ wrapper = shallowMount(WorkItemTypeIcon, {
+ propsData,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
}
describe('Work Item type component', () => {
@@ -16,22 +22,23 @@ describe('Work Item type component', () => {
});
describe.each`
- workItemType | workItemIconName | iconName | text
- ${'TASK'} | ${''} | ${'issue-type-task'} | ${'Task'}
- ${''} | ${'issue-type-task'} | ${'issue-type-task'} | ${''}
- ${'ISSUE'} | ${''} | ${'issue-type-issue'} | ${'Issue'}
- ${''} | ${'issue-type-issue'} | ${'issue-type-issue'} | ${''}
- ${'REQUIREMENTS'} | ${''} | ${'issue-type-requirements'} | ${'Requirements'}
- ${'INCIDENT'} | ${''} | ${'issue-type-incident'} | ${'Incident'}
- ${'TEST_CASE'} | ${''} | ${'issue-type-test-case'} | ${'Test case'}
- ${'random-issue-type'} | ${''} | ${'issue-type-issue'} | ${''}
+ workItemType | workItemIconName | iconName | text | showTooltipOnHover
+ ${'TASK'} | ${''} | ${'issue-type-task'} | ${'Task'} | ${false}
+ ${''} | ${'issue-type-task'} | ${'issue-type-task'} | ${''} | ${true}
+ ${'ISSUE'} | ${''} | ${'issue-type-issue'} | ${'Issue'} | ${true}
+ ${''} | ${'issue-type-issue'} | ${'issue-type-issue'} | ${''} | ${true}
+ ${'REQUIREMENTS'} | ${''} | ${'issue-type-requirements'} | ${'Requirements'} | ${true}
+ ${'INCIDENT'} | ${''} | ${'issue-type-incident'} | ${'Incident'} | ${false}
+ ${'TEST_CASE'} | ${''} | ${'issue-type-test-case'} | ${'Test case'} | ${true}
+ ${'random-issue-type'} | ${''} | ${'issue-type-issue'} | ${''} | ${true}
`(
'with workItemType set to "$workItemType" and workItemIconName set to "$workItemIconName"',
- ({ workItemType, workItemIconName, iconName, text }) => {
+ ({ workItemType, workItemIconName, iconName, text, showTooltipOnHover }) => {
beforeEach(() => {
createComponent({
workItemType,
workItemIconName,
+ showTooltipOnHover,
});
});
@@ -42,6 +49,16 @@ describe('Work Item type component', () => {
it(`renders correct text`, () => {
expect(wrapper.text()).toBe(text);
});
+
+ it('renders the icon in gray color', () => {
+ expect(findIcon().classes()).toContain('gl-text-gray-500');
+ });
+
+ it('shows tooltip on hover when props passed', () => {
+ const tooltip = getBinding(findIcon().element, 'gl-tooltip');
+
+ expect(tooltip.value).toBe(showTooltipOnHover);
+ });
},
);
});
diff --git a/spec/frontend/work_items/components/work_item_weight_spec.js b/spec/frontend/work_items/components/work_item_weight_spec.js
deleted file mode 100644
index 94bdb336deb..00000000000
--- a/spec/frontend/work_items/components/work_item_weight_spec.js
+++ /dev/null
@@ -1,214 +0,0 @@
-import { GlForm, GlFormInput } from '@gitlab/ui';
-import Vue, { nextTick } from 'vue';
-import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { mockTracking } from 'helpers/tracking_helper';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import { __ } from '~/locale';
-import WorkItemWeight from '~/work_items/components/work_item_weight.vue';
-import { i18n, TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
-import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
-import { updateWorkItemMutationResponse } from 'jest/work_items/mock_data';
-
-describe('WorkItemWeight component', () => {
- Vue.use(VueApollo);
-
- let wrapper;
-
- const workItemId = 'gid://gitlab/WorkItem/1';
- const workItemType = 'Task';
-
- const findForm = () => wrapper.findComponent(GlForm);
- const findInput = () => wrapper.findComponent(GlFormInput);
-
- const createComponent = ({
- canUpdate = false,
- hasIssueWeightsFeature = true,
- isEditing = false,
- weight,
- mutationHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse),
- } = {}) => {
- wrapper = mountExtended(WorkItemWeight, {
- apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]),
- propsData: {
- canUpdate,
- weight,
- workItemId,
- workItemType,
- },
- provide: {
- hasIssueWeightsFeature,
- },
- });
-
- if (isEditing) {
- findInput().vm.$emit('focus');
- }
- };
-
- describe('`issue_weights` licensed feature', () => {
- describe.each`
- description | hasIssueWeightsFeature | exists
- ${'when available'} | ${true} | ${true}
- ${'when not available'} | ${false} | ${false}
- `('$description', ({ hasIssueWeightsFeature, exists }) => {
- it(hasIssueWeightsFeature ? 'renders component' : 'does not render component', () => {
- createComponent({ hasIssueWeightsFeature });
-
- expect(findForm().exists()).toBe(exists);
- });
- });
- });
-
- describe('weight input', () => {
- it('has "Weight" label', () => {
- createComponent();
-
- expect(wrapper.findByLabelText(__('Weight')).exists()).toBe(true);
- });
-
- describe('placeholder attribute', () => {
- describe.each`
- description | isEditing | canUpdate | value
- ${'when not editing and cannot update'} | ${false} | ${false} | ${__('None')}
- ${'when editing and cannot update'} | ${true} | ${false} | ${__('None')}
- ${'when not editing and can update'} | ${false} | ${true} | ${__('None')}
- ${'when editing and can update'} | ${true} | ${true} | ${__('Enter a number')}
- `('$description', ({ isEditing, canUpdate, value }) => {
- it(`has a value of "${value}"`, async () => {
- createComponent({ canUpdate, isEditing });
- await nextTick();
-
- expect(findInput().attributes('placeholder')).toBe(value);
- });
- });
- });
-
- describe('readonly attribute', () => {
- describe.each`
- description | canUpdate | value
- ${'when cannot update'} | ${false} | ${'readonly'}
- ${'when can update'} | ${true} | ${undefined}
- `('$description', ({ canUpdate, value }) => {
- it(`renders readonly=${value}`, () => {
- createComponent({ canUpdate });
-
- expect(findInput().attributes('readonly')).toBe(value);
- });
- });
- });
-
- describe('type attribute', () => {
- describe.each`
- description | isEditing | canUpdate | type
- ${'when not editing and cannot update'} | ${false} | ${false} | ${'text'}
- ${'when editing and cannot update'} | ${true} | ${false} | ${'text'}
- ${'when not editing and can update'} | ${false} | ${true} | ${'text'}
- ${'when editing and can update'} | ${true} | ${true} | ${'number'}
- `('$description', ({ isEditing, canUpdate, type }) => {
- it(`has a value of "${type}"`, async () => {
- createComponent({ canUpdate, isEditing });
- await nextTick();
-
- expect(findInput().attributes('type')).toBe(type);
- });
- });
- });
-
- describe('value attribute', () => {
- describe.each`
- weight | value
- ${1} | ${'1'}
- ${0} | ${'0'}
- ${null} | ${''}
- ${undefined} | ${''}
- `('when `weight` prop is "$weight"', ({ weight, value }) => {
- it(`value is "${value}"`, () => {
- createComponent({ weight });
-
- expect(findInput().element.value).toBe(value);
- });
- });
- });
-
- describe('when blurred', () => {
- it('calls a mutation to update the weight when the input value is different', () => {
- const mutationSpy = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
- createComponent({
- isEditing: true,
- weight: 0,
- mutationHandler: mutationSpy,
- canUpdate: true,
- });
-
- findInput().vm.$emit('blur', { target: { value: 1 } });
-
- expect(mutationSpy).toHaveBeenCalledWith({
- input: {
- id: workItemId,
- weightWidget: {
- weight: 1,
- },
- },
- });
- });
-
- it('does not call a mutation to update the weight when the input value is the same', () => {
- const mutationSpy = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
- createComponent({ isEditing: true, mutationHandler: mutationSpy, canUpdate: true });
-
- findInput().trigger('blur');
-
- expect(mutationSpy).not.toHaveBeenCalledWith();
- });
-
- it('emits an error when there is a GraphQL error', async () => {
- const response = {
- data: {
- workItemUpdate: {
- errors: ['Error!'],
- workItem: {},
- },
- },
- };
- createComponent({
- isEditing: true,
- mutationHandler: jest.fn().mockResolvedValue(response),
- canUpdate: true,
- });
-
- findInput().trigger('blur');
- await waitForPromises();
-
- expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
- });
-
- it('emits an error when there is a network error', async () => {
- createComponent({
- isEditing: true,
- mutationHandler: jest.fn().mockRejectedValue(new Error()),
- canUpdate: true,
- });
-
- findInput().trigger('blur');
- await waitForPromises();
-
- expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
- });
-
- it('tracks updating the weight', () => {
- const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- createComponent({ canUpdate: true });
-
- findInput().trigger('blur');
-
- expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_weight', {
- category: TRACKING_CATEGORY_SHOW,
- label: 'item_weight',
- property: 'type_Task',
- });
- });
- });
- });
-});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index d24ac2a9f93..e1bc8d2f6b7 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -28,6 +28,11 @@ export const workItemQueryResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
workItemType: {
__typename: 'WorkItemType',
id: 'gid://gitlab/WorkItems::Type/5',
@@ -93,6 +98,11 @@ export const updateWorkItemMutationResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
workItemType: {
__typename: 'WorkItemType',
id: 'gid://gitlab/WorkItems::Type/5',
@@ -128,6 +138,16 @@ export const updateWorkItemMutationResponse = {
},
};
+export const updateWorkItemMutationErrorResponse = {
+ data: {
+ workItemUpdate: {
+ __typename: 'WorkItemUpdatePayload',
+ errors: ['Error!'],
+ workItem: {},
+ },
+ },
+};
+
export const mockParent = {
parent: {
id: 'gid://gitlab/Issue/1',
@@ -142,6 +162,7 @@ export const workItemResponseFactory = ({
canDelete = false,
allowsMultipleAssignees = true,
assigneesWidgetPresent = true,
+ datesWidgetPresent = true,
weightWidgetPresent = true,
confidential = false,
canInviteMembers = false,
@@ -157,6 +178,11 @@ export const workItemResponseFactory = ({
confidential,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
workItemType: {
__typename: 'WorkItemType',
id: 'gid://gitlab/WorkItems::Type/5',
@@ -186,6 +212,14 @@ export const workItemResponseFactory = ({
},
}
: { type: 'MOCK TYPE' },
+ datesWidgetPresent
+ ? {
+ __typename: 'WorkItemWidgetStartAndDueDate',
+ type: 'START_AND_DUE_DATE',
+ dueDate: '2022-12-31',
+ startDate: '2022-01-01',
+ }
+ : { type: 'MOCK TYPE' },
weightWidgetPresent
? {
__typename: 'WorkItemWidgetWeight',
@@ -212,17 +246,6 @@ export const workItemResponseFactory = ({
},
});
-export const updateWorkItemWidgetsResponse = {
- data: {
- workItemUpdateWidgets: {
- workItem: {
- id: 1234,
- },
- errors: [],
- },
- },
-};
-
export const projectWorkItemTypesQueryResponse = {
data: {
workspace: {
@@ -251,6 +274,11 @@ export const createWorkItemMutationResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
workItemType: {
__typename: 'WorkItemType',
id: 'gid://gitlab/WorkItems::Type/5',
@@ -282,6 +310,11 @@ export const createWorkItemFromTaskMutationResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
workItemType: {
__typename: 'WorkItemType',
id: 'gid://gitlab/WorkItems::Type/5',
@@ -310,6 +343,11 @@ export const createWorkItemFromTaskMutationResponse = {
closedAt: null,
description: '',
confidential: false,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
workItemType: {
__typename: 'WorkItemType',
id: 'gid://gitlab/WorkItems::Type/5',
@@ -368,6 +406,21 @@ export const deleteWorkItemFromTaskMutationErrorResponse = {
},
};
+export const workItemDatesSubscriptionResponse = {
+ data: {
+ issuableDatesUpdated: {
+ id: 'gid://gitlab/WorkItem/1',
+ widgets: [
+ {
+ __typename: 'WorkItemWidgetStartAndDueDate',
+ dueDate: '2022-12-31',
+ startDate: '2022-01-01',
+ },
+ ],
+ },
+ },
+};
+
export const workItemTitleSubscriptionResponse = {
data: {
issuableTitleUpdated: {
@@ -377,6 +430,20 @@ export const workItemTitleSubscriptionResponse = {
},
};
+export const workItemWeightSubscriptionResponse = {
+ data: {
+ issuableWeightUpdated: {
+ id: 'gid://gitlab/WorkItem/1',
+ widgets: [
+ {
+ __typename: 'WorkItemWidgetWeight',
+ weight: 1,
+ },
+ ],
+ },
+ },
+};
+
export const workItemHierarchyEmptyResponse = {
data: {
workItem: {
@@ -388,6 +455,11 @@ export const workItemHierarchyEmptyResponse = {
title: 'New title',
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
@@ -426,6 +498,11 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
deleteWorkItem: false,
updateWorkItem: false,
},
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
confidential: false,
widgets: [
{
@@ -461,6 +538,48 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
},
};
+export const workItemTask = {
+ id: 'gid://gitlab/WorkItem/4',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/5',
+ __typename: 'WorkItemType',
+ },
+ title: 'bar',
+ state: 'OPEN',
+ confidential: false,
+ createdAt: '2022-08-03T12:41:54Z',
+ closedAt: null,
+ __typename: 'WorkItem',
+};
+
+export const confidentialWorkItemTask = {
+ id: 'gid://gitlab/WorkItem/2',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/5',
+ __typename: 'WorkItemType',
+ },
+ title: 'xyz',
+ state: 'OPEN',
+ confidential: true,
+ createdAt: '2022-08-03T12:41:54Z',
+ closedAt: null,
+ __typename: 'WorkItem',
+};
+
+export const closedWorkItemTask = {
+ id: 'gid://gitlab/WorkItem/3',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/5',
+ __typename: 'WorkItemType',
+ },
+ title: 'abc',
+ state: 'CLOSED',
+ confidential: false,
+ createdAt: '2022-08-03T12:41:54Z',
+ closedAt: '2022-08-12T13:07:52Z',
+ __typename: 'WorkItem',
+};
+
export const workItemHierarchyResponse = {
data: {
workItem: {
@@ -475,6 +594,11 @@ export const workItemHierarchyResponse = {
updateWorkItem: true,
},
confidential: false,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
widgets: [
{
type: 'DESCRIPTION',
@@ -485,45 +609,9 @@ export const workItemHierarchyResponse = {
parent: null,
children: {
nodes: [
- {
- id: 'gid://gitlab/WorkItem/2',
- workItemType: {
- id: 'gid://gitlab/WorkItems::Type/5',
- __typename: 'WorkItemType',
- },
- title: 'xyz',
- state: 'OPEN',
- confidential: true,
- createdAt: '2022-08-03T12:41:54Z',
- closedAt: null,
- __typename: 'WorkItem',
- },
- {
- id: 'gid://gitlab/WorkItem/3',
- workItemType: {
- id: 'gid://gitlab/WorkItems::Type/5',
- __typename: 'WorkItemType',
- },
- title: 'abc',
- state: 'CLOSED',
- confidential: false,
- createdAt: '2022-08-03T12:41:54Z',
- closedAt: '2022-08-12T13:07:52Z',
- __typename: 'WorkItem',
- },
- {
- id: 'gid://gitlab/WorkItem/4',
- workItemType: {
- id: 'gid://gitlab/WorkItems::Type/5',
- __typename: 'WorkItemType',
- },
- title: 'bar',
- state: 'OPEN',
- confidential: false,
- createdAt: '2022-08-03T12:41:54Z',
- closedAt: null,
- __typename: 'WorkItem',
- },
+ confidentialWorkItemTask,
+ closedWorkItemTask,
+ workItemTask,
{
id: 'gid://gitlab/WorkItem/5',
workItemType: {
@@ -570,6 +658,11 @@ export const changeWorkItemParentMutationResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
widgets: [
{
__typename: 'WorkItemWidgetHierarchy',
@@ -649,6 +742,71 @@ export const projectMembersResponseWithCurrentUser = {
},
},
],
+ pageInfo: {
+ hasNextPage: false,
+ endCursor: null,
+ startCursor: null,
+ },
+ },
+ },
+ },
+};
+
+export const projectMembersResponseWithCurrentUserWithNextPage = {
+ data: {
+ workspace: {
+ id: '1',
+ __typename: 'Project',
+ users: {
+ nodes: [
+ {
+ id: 'user-2',
+ user: {
+ __typename: 'UserCore',
+ id: 'gid://gitlab/User/5',
+ avatarUrl: '/avatar2',
+ name: 'rookie',
+ username: 'rookie',
+ webUrl: 'rookie',
+ status: null,
+ },
+ },
+ {
+ id: 'user-1',
+ user: {
+ __typename: 'UserCore',
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: '/root',
+ status: null,
+ },
+ },
+ ],
+ pageInfo: {
+ hasNextPage: true,
+ endCursor: 'endCursor',
+ startCursor: 'startCursor',
+ },
+ },
+ },
+ },
+};
+
+export const projectMembersResponseWithNoMatchingUsers = {
+ data: {
+ workspace: {
+ id: '1',
+ __typename: 'Project',
+ users: {
+ nodes: [],
+ pageInfo: {
+ endCursor: null,
+ hasNextPage: false,
+ startCursor: null,
+ },
},
},
},
diff --git a/spec/frontend/work_items/pages/create_work_item_spec.js b/spec/frontend/work_items/pages/create_work_item_spec.js
index fed8be3783a..15dac25b7d9 100644
--- a/spec/frontend/work_items/pages/create_work_item_spec.js
+++ b/spec/frontend/work_items/pages/create_work_item_spec.js
@@ -193,6 +193,8 @@ describe('Create work item component', () => {
wrapper.find('form').trigger('submit');
await waitForPromises();
- expect(findAlert().text()).toBe(CreateWorkItem.createErrorText);
+ expect(findAlert().text()).toBe(
+ 'Something went wrong when creating work item. Please try again.',
+ );
});
});
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index 99dcd886f7b..ab370e2ca8b 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -1,5 +1,18 @@
import { mount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import workItemWeightSubscription from 'ee_component/work_items/graphql/work_item_weight.subscription.graphql';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import {
+ workItemDatesSubscriptionResponse,
+ workItemResponseFactory,
+ workItemTitleSubscriptionResponse,
+ workItemWeightSubscriptionResponse,
+} from 'jest/work_items/mock_data';
import App from '~/work_items/components/app.vue';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+import workItemDatesSubscription from '~/work_items/graphql/work_item_dates.subscription.graphql';
+import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
import { createRouter } from '~/work_items/router';
@@ -7,26 +20,36 @@ import { createRouter } from '~/work_items/router';
describe('Work items router', () => {
let wrapper;
+ Vue.use(VueApollo);
+
+ const workItemQueryHandler = jest.fn().mockResolvedValue(workItemResponseFactory());
+ const datesSubscriptionHandler = jest.fn().mockResolvedValue(workItemDatesSubscriptionResponse);
+ const titleSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
+ const weightSubscriptionHandler = jest.fn().mockResolvedValue(workItemWeightSubscriptionResponse);
+
const createComponent = async (routeArg) => {
const router = createRouter('/work_item');
if (routeArg !== undefined) {
await router.push(routeArg);
}
+ const handlers = [
+ [workItemQuery, workItemQueryHandler],
+ [workItemDatesSubscription, datesSubscriptionHandler],
+ [workItemTitleSubscription, titleSubscriptionHandler],
+ ];
+
+ if (IS_EE) {
+ handlers.push([workItemWeightSubscription, weightSubscriptionHandler]);
+ }
+
wrapper = mount(App, {
+ apolloProvider: createMockApollo(handlers),
router,
provide: {
fullPath: 'full-path',
issuesListPath: 'full-path/-/issues',
},
- mocks: {
- $apollo: {
- queries: {
- workItem: {},
- workItemTypes: {},
- },
- },
- },
});
};
diff --git a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
index 67420e7fc2a..dca016dc317 100644
--- a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
+++ b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
@@ -74,7 +74,7 @@ describe('WorkItemsHierarchy Hierarchy', () => {
});
it('renders license badges for all work items', () => {
- expect(wrapper.findAll(GlBadge)).toHaveLength(items.length);
+ expect(wrapper.findAllComponents(GlBadge)).toHaveLength(items.length);
});
it('does not render svg icon for linking', () => {