From 9dc93a4519d9d5d7be48ff274127136236a3adb3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 20 Apr 2021 23:50:22 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-11-stable-ee --- .../frontend/__helpers__/experimentation_helper.js | 13 + spec/frontend/__helpers__/mock_apollo_helper.js | 12 +- spec/frontend/__helpers__/vue_test_utils_helper.js | 73 ++- .../__helpers__/vue_test_utils_helper_spec.js | 208 +++++- spec/frontend/__helpers__/web_worker_fake.js | 71 +++ spec/frontend/__helpers__/web_worker_mock.js | 10 - .../frontend/__helpers__/web_worker_transformer.js | 18 + spec/frontend/__mocks__/vue/index.js | 7 + spec/frontend/access_tokens/index_spec.js | 20 +- .../components/signup_checkbox_spec.js | 66 ++ .../components/signup_form_spec.js | 331 ++++++++++ .../admin/signup_restrictions/mock_data.js | 41 ++ spec/frontend/admin/signup_restrictions/utils.js | 19 + .../admin/signup_restrictions/utils_spec.js | 22 + .../admin/users/components/user_date_spec.js | 2 +- .../admin/users/components/users_table_spec.js | 2 +- spec/frontend/admin/users/new_spec.js | 76 +++ .../alerts_settings_form_spec.js.snap | 524 --------------- .../components/alerts_settings_form_spec.js | 258 +++++--- .../components/alerts_settings_wrapper_spec.js | 120 ++-- .../analytics/usage_trends/components/app_spec.js | 2 +- spec/frontend/api_spec.js | 68 +- .../batch_comments/components/preview_item_spec.js | 12 + .../behaviors/markdown/render_mermaid_spec.js | 25 + .../behaviors/shortcuts/shortcuts_issuable_spec.js | 2 +- spec/frontend/blob/file_template_selector_spec.js | 61 ++ spec/frontend/boards/board_card_inner_spec.js | 87 ++- .../boards/board_new_issue_deprecated_spec.js | 16 +- .../__snapshots__/board_blocked_icon_spec.js.snap | 30 + .../components/board_add_new_column_form_spec.js | 32 +- .../boards/components/board_add_new_column_spec.js | 10 + .../boards/components/board_blocked_icon_spec.js | 226 +++++++ .../components/board_content_sidebar_spec.js | 140 ++++ .../boards/components/board_content_spec.js | 13 +- spec/frontend/boards/components/board_form_spec.js | 2 +- .../boards/components/board_new_issue_spec.js | 6 +- .../components/board_settings_sidebar_spec.js | 43 +- .../boards/components/filtered_search_spec.js | 65 -- .../boards/components/issue_time_estimate_spec.js | 6 +- .../sidebar/board_sidebar_issue_title_spec.js | 182 ------ .../sidebar/board_sidebar_labels_select_spec.js | 14 +- .../sidebar/board_sidebar_subscription_spec.js | 28 +- .../sidebar/board_sidebar_time_tracker_spec.js | 58 ++ .../components/sidebar/board_sidebar_title_spec.js | 182 ++++++ spec/frontend/boards/mock_data.js | 130 +++- spec/frontend/boards/modal_store_spec.js | 134 ---- spec/frontend/boards/stores/actions_spec.js | 708 ++++++++++++++++----- spec/frontend/boards/stores/getters_spec.js | 41 +- spec/frontend/boards/stores/mutations_spec.js | 239 ++++--- .../branches/components/sort_dropdown_spec.js | 91 +++ spec/frontend/captcha/apollo_captcha_link_spec.js | 165 +++++ .../components/lock_popovers_spec.js | 152 +++++ .../components/ci_variable_modal_spec.js | 43 +- .../components/ci_variable_table_spec.js | 14 - .../clusters/components/application_row_spec.js | 6 + .../services/application_state_machine_spec.js | 30 +- .../components/content_editor_spec.js | 26 + .../content_editor/markdown_processing_examples.js | 19 + .../content_editor/markdown_processing_spec.js | 12 + .../content_editor/services/create_editor_spec.js | 39 ++ .../__snapshots__/contributors_spec.js.snap | 17 +- .../frontend/create_merge_request_dropdown_spec.js | 18 +- spec/frontend/cycle_analytics/banner_spec.js | 46 +- .../cycle_analytics/total_time_component_spec.js | 34 +- spec/frontend/delete_label_modal_spec.js | 83 +++ .../components/deploy_freeze_modal_spec.js | 71 ++- .../components/deploy_freeze_table_spec.js | 24 +- spec/frontend/deploy_freeze/store/actions_spec.js | 82 ++- .../frontend/deploy_freeze/store/mutations_spec.js | 15 +- .../deploy_tokens/components/revoke_button_spec.js | 108 ++++ .../__snapshots__/design_navigation_spec.js.snap | 2 + .../toolbar/__snapshots__/index_spec.js.snap | 1 + .../upload/__snapshots__/button_spec.js.snap | 8 +- spec/frontend/diffs/components/app_spec.js | 34 +- spec/frontend/diffs/components/commit_item_spec.js | 130 ---- .../diffs/components/compare_versions_spec.js | 135 +++- spec/frontend/diffs/components/diff_row_spec.js | 17 + .../diffs/components/inline_diff_table_row_spec.js | 13 + .../components/parallel_diff_table_row_spec.js | 23 + spec/frontend/diffs/create_diffs_store.js | 2 + spec/frontend/diffs/find_interop_attributes.js | 20 + spec/frontend/diffs/store/actions_spec.js | 47 +- spec/frontend/diffs/store/getters_spec.js | 44 +- spec/frontend/diffs/store/mutations_spec.js | 14 +- spec/frontend/diffs/utils/interoperability_spec.js | 67 ++ .../editor/editor_lite_extension_base_spec.js | 271 +++++++- .../emoji/awards_app/store/actions_spec.js | 155 +++++ .../emoji/awards_app/store/mutations_spec.js | 65 ++ .../environments/enable_review_app_modal_spec.js | 23 +- .../components/error_tracking_list_spec.js | 1 - .../components/error_tracking_form_spec.js | 12 +- .../error_tracking_settings/store/getters_spec.js | 4 +- .../experimentation/components/experiment_spec.js | 72 +++ spec/frontend/experimentation/utils_spec.js | 101 ++- .../frontend/feature_flags/components/form_spec.js | 4 + .../feature_highlight_popover_spec.js | 1 - spec/frontend/fixtures/api_markdown.rb | 34 + spec/frontend/fixtures/api_markdown.yml | 50 ++ spec/frontend/fixtures/autocomplete.rb | 41 ++ spec/frontend/fixtures/issues.rb | 2 - spec/frontend/fixtures/merge_requests_diffs.rb | 20 +- .../fixtures/static/mini_dropdown_graph.html | 13 - .../fixtures/static/whats_new_notification.html | 2 +- spec/frontend/flash_spec.js | 62 +- spec/frontend/gfm_auto_complete_spec.js | 35 + .../__snapshots__/grafana_integration_spec.js.snap | 2 +- .../ide/components/cannot_push_code_alert_spec.js | 72 +++ .../ide/components/commit_sidebar/form_spec.js | 6 +- spec/frontend/ide/components/ide_spec.js | 28 +- spec/frontend/ide/stores/getters_spec.js | 126 +++- .../incidents_settings_tabs_spec.js.snap | 4 +- .../edit/components/dynamic_field_spec.js | 11 - .../edit/components/jira_issues_fields_spec.js | 49 +- .../edit/components/jira_trigger_fields_spec.js | 106 ++- .../edit/components/jira_upgrade_cta_spec.js | 30 + .../edit/components/trigger_fields_spec.js | 4 +- .../index/components/integrations_list_spec.js | 26 + .../index/components/integrations_table_spec.js | 53 ++ spec/frontend/integrations/index/mock_data.js | 50 ++ .../components/invite_member_modal_spec.js | 2 +- .../components/invite_member_trigger_spec.js | 2 +- .../components/invite_members_modal_spec.js | 48 +- .../components/invite_members_trigger_spec.js | 82 ++- .../issuable/components/csv_export_modal_spec.js | 6 +- .../components/csv_import_export_buttons_spec.js | 9 +- .../components/issuable_list_root_spec.js | 124 +++- .../issuable_list/components/issuable_tabs_spec.js | 15 +- spec/frontend/issuable_list/mock_data.js | 2 +- spec/frontend/issuable_show/mock_data.js | 1 + .../__snapshots__/info_popover_spec.js.snap | 52 ++ .../components/info_popover_spec.js | 20 + .../issue_show/components/edit_actions_spec.js | 8 +- .../issues_list/components/issues_list_app_spec.js | 540 ++++++++++++++-- spec/frontend/jira_connect/api_spec.js | 17 +- .../__snapshots__/group_item_name_spec.js.snap | 44 ++ spec/frontend/jira_connect/components/app_spec.js | 30 +- .../components/group_item_name_spec.js | 28 + .../components/groups_list_item_spec.js | 57 +- .../jira_connect/components/groups_list_spec.js | 103 ++- .../components/subscriptions_list_spec.js | 122 ++++ spec/frontend/jira_connect/index_spec.js | 34 +- spec/frontend/jira_connect/mock_data.js | 6 + spec/frontend/jira_connect/utils_spec.js | 114 +++- spec/frontend/jobs/components/commit_block_spec.js | 105 ++- .../job_sidebar_details_container_spec.js | 15 +- .../jobs/components/manual_variables_form_spec.js | 153 +++-- spec/frontend/jobs/components/sidebar_spec.js | 30 + .../jobs/components/stages_dropdown_spec.js | 155 ++--- .../jobs/components/table/jobs_table_spec.js | 31 + .../jobs/components/table/jobs_table_tabs_spec.js | 42 ++ spec/frontend/jobs/mock_data.js | 215 +++++++ spec/frontend/lib/utils/color_utils_spec.js | 27 +- spec/frontend/lib/utils/common_utils_spec.js | 10 + spec/frontend/lib/utils/datetime_utility_spec.js | 80 +-- spec/frontend/lib/utils/forms_spec.js | 163 ++++- .../access_request_action_buttons_spec.js | 1 + .../approve_access_request_button_spec.js | 15 +- .../action_buttons/invite_action_buttons_spec.js | 2 + .../remove_group_link_button_spec.js | 11 +- .../action_buttons/remove_member_button_spec.js | 21 +- .../action_buttons/resend_invite_button_spec.js | 15 +- .../action_buttons/user_action_buttons_spec.js | 46 +- spec/frontend/members/components/app_spec.js | 25 +- .../members/components/avatars/user_avatar_spec.js | 16 +- .../filter_sort/filter_sort_container_spec.js | 27 +- .../members_filtered_search_bar_spec.js | 50 +- .../components/filter_sort/sort_dropdown_spec.js | 29 +- .../members/components/modals/leave_modal_spec.js | 42 +- .../modals/remove_group_link_modal_spec.js | 22 +- .../components/table/expiration_datepicker_spec.js | 10 +- .../components/table/members_table_cell_spec.js | 12 +- .../members/components/table/members_table_spec.js | 32 +- .../members/components/table/role_dropdown_spec.js | 10 +- spec/frontend/members/index_spec.js | 47 +- spec/frontend/members/mock_data.js | 2 + .../components/merge_conflict_resolver_app_spec.js | 131 ++++ spec/frontend/merge_conflicts/mock_data.js | 340 ++++++++++ .../frontend/merge_conflicts/store/actions_spec.js | 125 +++- .../frontend/merge_conflicts/store/getters_spec.js | 187 ++++++ .../merge_conflicts/store/mutations_spec.js | 99 +++ spec/frontend/merge_conflicts/utils_spec.js | 106 +++ .../merge_request/components/status_box_spec.js | 2 +- spec/frontend/mini_pipeline_graph_dropdown_spec.js | 104 --- .../frontend/mocks/ce/diffs/workers/tree_worker.js | 1 - spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js | 1 - spec/frontend/mr_notes/stores/actions_spec.js | 92 +++ spec/frontend/mr_notes/stores/mutations_spec.js | 27 + spec/frontend/notebook/cells/markdown_spec.js | 106 +-- .../frontend/notes/components/comment_form_spec.js | 67 +- .../notes/components/discussion_navigator_spec.js | 13 +- .../frontend/notes/components/note_actions_spec.js | 16 + spec/frontend/notes/components/note_body_spec.js | 17 +- .../notes/components/noteable_discussion_spec.js | 9 +- .../notes/components/noteable_note_spec.js | 133 +++- spec/frontend/notes/components/notes_app_spec.js | 30 + spec/frontend/notes/mock_data.js | 16 + spec/frontend/notes/stores/getters_spec.js | 69 +- .../packages/details/store/getters_spec.js | 2 + .../__snapshots__/packages_list_app_spec.js.snap | 10 +- .../list/components/packages_list_app_spec.js | 133 +++- .../list/components/packages_search_spec.js | 29 +- .../list/components/packages_title_spec.js | 18 +- spec/frontend/packages/list/utils_spec.js | 11 +- spec/frontend/packages/mock_data.js | 17 + .../__snapshots__/package_list_row_spec.js.snap | 15 +- .../components/package_icon_and_name_spec.js | 32 + .../shared/components/package_list_row_spec.js | 41 +- spec/frontend/packages/shared/utils_spec.js | 1 + .../infrastructure_icon_and_name_spec.js | 28 + .../components/infrastructure_search_spec.js | 135 ++++ .../components/infrastructure_title_spec.js | 75 +++ .../group/components/maven_settings_spec.js | 5 +- .../packages_and_registries/shared/utils_spec.js | 59 ++ spec/frontend/pager_spec.js | 71 ++- .../__snapshots__/delete_user_modal_spec.js.snap | 4 +- .../users/components/delete_user_modal_spec.js | 8 +- spec/frontend/pages/admin/users/new/index_spec.js | 41 -- .../forks/new/components/fork_form_spec.js | 35 +- .../__snapshots__/learn_gitlab_a_spec.js.snap | 374 +++++++++-- .../__snapshots__/learn_gitlab_b_spec.js.snap | 70 +- .../learn_gitlab_section_card_spec.js.snap | 67 ++ .../learn_gitlab/components/learn_gitlab_a_spec.js | 30 +- .../learn_gitlab/components/learn_gitlab_b_spec.js | 6 +- .../components/learn_gitlab_section_card_spec.js | 26 + .../components/learn_gitlab_section_link_spec.js | 49 ++ .../projects/learn_gitlab/components/mock_data.js | 5 + .../permissions/components/settings_panel_spec.js | 45 +- .../shared/wikis/components/wiki_alert_spec.js | 40 ++ .../shared/wikis/components/wiki_form_spec.js | 222 +++++++ .../frontend/pages/shared/wikis/wiki_alert_spec.js | 40 -- .../components/detailed_metric_spec.js | 304 +++++++-- .../stores/performance_bar_store_spec.js | 40 ++ .../code_snippet_alert/code_snippet_alert_spec.js | 61 ++ .../editor/ci_config_merged_preview_spec.js | 21 +- .../components/file-nav/branch_switcher_spec.js | 123 ++++ .../file-nav/pipeline_editor_file_nav_spec.js | 49 ++ .../header/pipeline_editor_header_spec.js | 16 +- .../components/header/pipeline_status_spec.js | 55 +- .../components/header/validation_segment_spec.js | 93 +-- .../components/lint/ci_lint_results_spec.js | 5 +- .../components/lint/ci_lint_spec.js | 17 +- .../components/pipeline_editor_tabs_spec.js | 54 +- .../components/ui/editor_tab_spec.js | 57 +- .../pipeline_editor/graphql/resolvers_spec.js | 18 +- spec/frontend/pipeline_editor/mock_data.js | 14 + .../pipeline_editor/pipeline_editor_app_spec.js | 91 ++- .../pipeline_editor/pipeline_editor_home_spec.js | 7 + spec/frontend/pipelines/blank_state_spec.js | 20 - .../pipelines_list/pipeline_stage_spec.js | 9 + .../pipelines/graph/action_component_spec.js | 2 +- .../pipelines/graph/graph_component_spec.js | 31 +- .../graph/graph_component_wrapper_spec.js | 172 ++++- .../pipelines/graph/job_group_dropdown_spec.js | 8 +- spec/frontend/pipelines/graph/job_item_spec.js | 4 +- .../pipelines/graph/job_name_component_spec.js | 2 +- .../graph/linked_pipelines_column_spec.js | 33 +- spec/frontend/pipelines/graph/mock_data.js | 18 +- .../pipelines/graph/stage_column_component_spec.js | 30 +- .../pipelines/graph_shared/links_inner_spec.js | 2 +- .../pipelines/graph_shared/links_layer_spec.js | 13 +- spec/frontend/pipelines/nav_controls_spec.js | 60 +- .../notification/pipeline_notification_spec.js | 79 +++ .../frontend/pipelines/pipeline_graph/mock_data.js | 36 ++ .../pipeline_graph/pipeline_graph_spec.js | 60 +- .../pipelines/pipelines_ci_templates_spec.js | 111 ++++ spec/frontend/pipelines/pipelines_spec.js | 45 +- .../frontend/pipelines/pipelines_table_row_spec.js | 239 ------- spec/frontend/pipelines/pipelines_table_spec.js | 56 +- spec/frontend/pipelines/time_ago_spec.js | 69 +- spec/frontend/pipelines/unwrapping_utils_spec.js | 4 +- .../commit/components/branches_dropdown_spec.js | 6 +- .../components/commit_comments_button_spec.js | 42 ++ .../components/commit_options_dropdown_spec.js | 123 ++++ .../projects/commit/components/form_modal_spec.js | 11 +- .../commit/components/form_trigger_spec.js | 44 -- spec/frontend/projects/commit/mock_data.js | 2 +- .../projects/commit/store/mutations_spec.js | 6 +- .../projects/commit_box/info/load_branches_spec.js | 41 +- .../projects/compare/components/app_legacy_spec.js | 55 +- .../compare/components/repo_dropdown_spec.js | 22 +- .../components/revision_dropdown_legacy_spec.js | 25 +- .../compare/components/revision_dropdown_spec.js | 41 +- .../components/app_spec.js | 52 ++ .../components/welcome_spec.js | 31 +- .../ci_cd_analytics_area_chart_spec.js.snap | 6 +- .../pipelines/charts/components/app_spec.js | 57 +- spec/frontend/registry/explorer/pages/list_spec.js | 91 ++- .../settings/components/settings_form_spec.js | 82 ++- .../releases/components/app_edit_new_spec.js | 10 +- .../frontend/releases/components/app_index_spec.js | 8 +- spec/frontend/releases/components/app_show_spec.js | 189 ++++-- .../releases/components/asset_links_form_spec.js | 2 +- .../release_block_milestone_info_spec.js | 2 +- .../components/releases_pagination_graphql_spec.js | 16 +- .../components/releases_pagination_rest_spec.js | 14 +- .../releases/components/releases_sort_spec.js | 12 +- .../releases/components/tag_field_exsting_spec.js | 6 +- .../releases/components/tag_field_new_spec.js | 14 +- .../frontend/releases/components/tag_field_spec.js | 6 +- .../releases/stores/modules/detail/actions_spec.js | 10 +- .../releases/stores/modules/detail/getters_spec.js | 4 +- .../stores/modules/detail/mutations_spec.js | 8 +- .../releases/stores/modules/list/actions_spec.js | 6 +- .../releases/stores/modules/list/helpers.js | 2 +- .../releases/stores/modules/list/mutations_spec.js | 6 +- .../reports/components/report_section_spec.js | 22 +- .../grouped_test_report/components/modal_spec.js | 9 +- .../components/test_issue_body_spec.js | 2 +- .../grouped_test_reports_app_spec.js | 41 +- .../grouped_test_report/store/actions_spec.js | 17 +- .../grouped_test_report/store/mutations_spec.js | 8 +- .../grouped_test_report/store/utils_spec.js | 14 + .../directory_download_links_spec.js.snap | 40 +- .../components/blob_content_viewer_spec.js | 86 +++ .../repository/components/breadcrumbs_spec.js | 84 ++- .../repository/components/table/row_spec.js | 5 +- spec/frontend/repository/pages/blob_spec.js | 25 + spec/frontend/repository/router_spec.js | 2 + .../runner/runner_detail/runner_detail_app_spec.js | 29 + .../configuration_table_spec.js | 2 +- .../security_configuration/manage_sast_spec.js | 2 +- .../__snapshots__/empty_state_spec.js.snap | 4 +- .../set_status_modal_wrapper_spec.js | 58 +- spec/frontend/sidebar/assignees_realtime_spec.js | 15 +- .../assignees/sidebar_assignees_widget_spec.js | 558 ++++++++++++++++ .../assignees/sidebar_editable_item_spec.js | 33 +- .../assignees/sidebar_invite_members_spec.js | 59 ++ .../assignees/sidebar_participant_spec.js | 43 ++ .../sidebar_confidentiality_form_spec.js | 4 +- .../sidebar_confidentiality_widget_spec.js | 4 +- .../components/copy_email_to_clipboard_spec.js | 19 +- .../due_date/sidebar_due_date_widget_spec.js | 106 +++ .../reference/sidebar_reference_widget_spec.js | 70 +- spec/frontend/sidebar/issuable_assignees_spec.js | 19 +- spec/frontend/sidebar/mock_data.js | 156 +++++ .../snippet_description_edit_spec.js.snap | 2 + spec/frontend/snippets/components/edit_spec.js | 71 +-- .../frontend/tags/components/sort_dropdown_spec.js | 81 +++ spec/frontend/tracking_spec.js | 76 ++- spec/frontend/users_select/index_spec.js | 223 +++++++ spec/frontend/vue_alerts_spec.js | 8 +- .../components/mr_widget_author_time_spec.js | 43 +- .../components/mr_widget_header_spec.js | 156 +++-- .../components/mr_widget_pipeline_spec.js | 91 +-- .../states/mr_widget_auto_merge_enabled_spec.js | 29 +- .../components/states/mr_widget_conflicts_spec.js | 19 +- .../states/mr_widget_failed_to_merge_spec.js | 166 +++-- .../vue_shared/alert_details/alert_details_spec.js | 10 +- .../vue_shared/alert_details/alert_status_spec.js | 19 + .../alert_details/sidebar/alert_sidebar_spec.js | 16 - .../sidebar/alert_sidebar_status_spec.js | 31 +- .../__snapshots__/clone_dropdown_spec.js.snap | 2 + .../components/alert_details_table_spec.js | 83 ++- .../__snapshots__/simple_viewer_spec.js.snap | 141 ++-- .../components/blob_viewers/simple_viewer_spec.js | 41 +- .../components/delete_label_modal_spec.js | 64 ++ .../vue_shared/components/deprecated_modal_spec.js | 73 --- .../vue_shared/components/ensure_data_spec.js | 145 +++++ .../components/filtered_search_bar/mock_data.js | 47 ++ .../filtered_search_bar/tokens/emoji_token_spec.js | 217 +++++++ .../filtered_search_bar/tokens/epic_token_spec.js | 180 ++++++ .../filtered_search_bar/tokens/label_token_spec.js | 16 + .../vue_shared/components/gl_toggle_vuex_spec.js | 114 ---- .../vue_shared/components/help_popover_spec.js | 14 +- .../components/lib/utils/props_utils_spec.js | 91 +++ .../markdown/suggestion_diff_header_spec.js | 34 +- .../vue_shared/components/markdown/toolbar_spec.js | 64 +- .../components/recaptcha_eventhub_spec.js | 21 - .../vue_shared/components/recaptcha_modal_spec.js | 35 - .../components/registry/registry_search_spec.js | 57 +- .../components/remove_member_modal_spec.js | 61 +- .../components/runner_instructions/mock_data.js | 16 +- .../runner_instructions_modal_spec.js | 184 ++++++ .../runner_instructions_spec.js | 110 +--- .../components/sidebar/copyable_field_spec.js | 74 +++ .../vue_shared/components/url_sync_spec.js | 97 +++ .../components/user_popover/user_popover_spec.js | 6 +- .../vue_shared/oncall_schedules_list_spec.js | 87 +++ spec/frontend/whats_new/components/app_spec.js | 59 +- spec/frontend/whats_new/store/actions_spec.js | 11 +- spec/frontend/whats_new/utils/notification_spec.js | 23 +- spec/frontend/wikis_spec.js | 153 ----- 382 files changed, 16904 insertions(+), 5177 deletions(-) create mode 100644 spec/frontend/__helpers__/web_worker_fake.js delete mode 100644 spec/frontend/__helpers__/web_worker_mock.js create mode 100644 spec/frontend/__helpers__/web_worker_transformer.js create mode 100644 spec/frontend/__mocks__/vue/index.js create mode 100644 spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js create mode 100644 spec/frontend/admin/signup_restrictions/components/signup_form_spec.js create mode 100644 spec/frontend/admin/signup_restrictions/mock_data.js create mode 100644 spec/frontend/admin/signup_restrictions/utils.js create mode 100644 spec/frontend/admin/signup_restrictions/utils_spec.js create mode 100644 spec/frontend/admin/users/new_spec.js delete mode 100644 spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap create mode 100644 spec/frontend/behaviors/markdown/render_mermaid_spec.js create mode 100644 spec/frontend/blob/file_template_selector_spec.js create mode 100644 spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap create mode 100644 spec/frontend/boards/components/board_blocked_icon_spec.js create mode 100644 spec/frontend/boards/components/board_content_sidebar_spec.js delete mode 100644 spec/frontend/boards/components/filtered_search_spec.js delete mode 100644 spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js create mode 100644 spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js create mode 100644 spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js delete mode 100644 spec/frontend/boards/modal_store_spec.js create mode 100644 spec/frontend/branches/components/sort_dropdown_spec.js create mode 100644 spec/frontend/captcha/apollo_captcha_link_spec.js create mode 100644 spec/frontend/cascading_settings/components/lock_popovers_spec.js create mode 100644 spec/frontend/content_editor/components/content_editor_spec.js create mode 100644 spec/frontend/content_editor/markdown_processing_examples.js create mode 100644 spec/frontend/content_editor/markdown_processing_spec.js create mode 100644 spec/frontend/content_editor/services/create_editor_spec.js create mode 100644 spec/frontend/delete_label_modal_spec.js create mode 100644 spec/frontend/deploy_tokens/components/revoke_button_spec.js create mode 100644 spec/frontend/diffs/find_interop_attributes.js create mode 100644 spec/frontend/diffs/utils/interoperability_spec.js create mode 100644 spec/frontend/emoji/awards_app/store/actions_spec.js create mode 100644 spec/frontend/emoji/awards_app/store/mutations_spec.js create mode 100644 spec/frontend/experimentation/components/experiment_spec.js create mode 100644 spec/frontend/fixtures/api_markdown.rb create mode 100644 spec/frontend/fixtures/api_markdown.yml create mode 100644 spec/frontend/fixtures/autocomplete.rb delete mode 100644 spec/frontend/fixtures/static/mini_dropdown_graph.html create mode 100644 spec/frontend/ide/components/cannot_push_code_alert_spec.js create mode 100644 spec/frontend/integrations/edit/components/jira_upgrade_cta_spec.js create mode 100644 spec/frontend/integrations/index/components/integrations_list_spec.js create mode 100644 spec/frontend/integrations/index/components/integrations_table_spec.js create mode 100644 spec/frontend/integrations/index/mock_data.js create mode 100644 spec/frontend/issuable_type_selector/components/__snapshots__/info_popover_spec.js.snap create mode 100644 spec/frontend/issuable_type_selector/components/info_popover_spec.js create mode 100644 spec/frontend/jira_connect/components/__snapshots__/group_item_name_spec.js.snap create mode 100644 spec/frontend/jira_connect/components/group_item_name_spec.js create mode 100644 spec/frontend/jira_connect/components/subscriptions_list_spec.js create mode 100644 spec/frontend/jobs/components/table/jobs_table_spec.js create mode 100644 spec/frontend/jobs/components/table/jobs_table_tabs_spec.js create mode 100644 spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js create mode 100644 spec/frontend/merge_conflicts/mock_data.js create mode 100644 spec/frontend/merge_conflicts/store/getters_spec.js create mode 100644 spec/frontend/merge_conflicts/store/mutations_spec.js create mode 100644 spec/frontend/merge_conflicts/utils_spec.js delete mode 100644 spec/frontend/mini_pipeline_graph_dropdown_spec.js delete mode 100644 spec/frontend/mocks/ce/diffs/workers/tree_worker.js delete mode 100644 spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js create mode 100644 spec/frontend/mr_notes/stores/actions_spec.js create mode 100644 spec/frontend/mr_notes/stores/mutations_spec.js create mode 100644 spec/frontend/packages/shared/components/package_icon_and_name_spec.js create mode 100644 spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js create mode 100644 spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js create mode 100644 spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js create mode 100644 spec/frontend/packages_and_registries/shared/utils_spec.js delete mode 100644 spec/frontend/pages/admin/users/new/index_spec.js create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js create mode 100644 spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js create mode 100644 spec/frontend/pages/shared/wikis/components/wiki_form_spec.js delete mode 100644 spec/frontend/pages/shared/wikis/wiki_alert_spec.js create mode 100644 spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js create mode 100644 spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js create mode 100644 spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js delete mode 100644 spec/frontend/pipelines/blank_state_spec.js create mode 100644 spec/frontend/pipelines/notification/pipeline_notification_spec.js create mode 100644 spec/frontend/pipelines/pipelines_ci_templates_spec.js delete mode 100644 spec/frontend/pipelines/pipelines_table_row_spec.js create mode 100644 spec/frontend/projects/commit/components/commit_comments_button_spec.js create mode 100644 spec/frontend/projects/commit/components/commit_options_dropdown_spec.js delete mode 100644 spec/frontend/projects/commit/components/form_trigger_spec.js create mode 100644 spec/frontend/repository/components/blob_content_viewer_spec.js create mode 100644 spec/frontend/repository/pages/blob_spec.js create mode 100644 spec/frontend/runner/runner_detail/runner_detail_app_spec.js create mode 100644 spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js create mode 100644 spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js create mode 100644 spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js create mode 100644 spec/frontend/sidebar/components/due_date/sidebar_due_date_widget_spec.js create mode 100644 spec/frontend/tags/components/sort_dropdown_spec.js create mode 100644 spec/frontend/users_select/index_spec.js create mode 100644 spec/frontend/vue_shared/components/delete_label_modal_spec.js delete mode 100644 spec/frontend/vue_shared/components/deprecated_modal_spec.js create mode 100644 spec/frontend/vue_shared/components/ensure_data_spec.js create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js delete mode 100644 spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js create mode 100644 spec/frontend/vue_shared/components/lib/utils/props_utils_spec.js delete mode 100644 spec/frontend/vue_shared/components/recaptcha_eventhub_spec.js delete mode 100644 spec/frontend/vue_shared/components/recaptcha_modal_spec.js create mode 100644 spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/copyable_field_spec.js create mode 100644 spec/frontend/vue_shared/components/url_sync_spec.js create mode 100644 spec/frontend/vue_shared/oncall_schedules_list_spec.js (limited to 'spec/frontend') diff --git a/spec/frontend/__helpers__/experimentation_helper.js b/spec/frontend/__helpers__/experimentation_helper.js index c08c25155e8..7a2ef61216a 100644 --- a/spec/frontend/__helpers__/experimentation_helper.js +++ b/spec/frontend/__helpers__/experimentation_helper.js @@ -12,3 +12,16 @@ export function withGonExperiment(experimentKey, value = true) { window.gon = origGon; }); } +// This helper is for specs that use `gitlab-experiment` utilities, which have a different schema that gets pushed via Gon compared to `Experimentation Module` +export function assignGitlabExperiment(experimentKey, variant) { + let origGon; + + beforeEach(() => { + origGon = window.gon; + window.gon = { experiment: { [experimentKey]: { variant } } }; + }); + + afterEach(() => { + window.gon = origGon; + }); +} diff --git a/spec/frontend/__helpers__/mock_apollo_helper.js b/spec/frontend/__helpers__/mock_apollo_helper.js index 914cce1d662..bd97a06071a 100644 --- a/spec/frontend/__helpers__/mock_apollo_helper.js +++ b/spec/frontend/__helpers__/mock_apollo_helper.js @@ -2,11 +2,15 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import { createMockClient } from 'mock-apollo-client'; import VueApollo from 'vue-apollo'; -export default (handlers = [], resolvers = {}) => { - const fragmentMatcher = { match: () => true }; +const defaultCacheOptions = { + fragmentMatcher: { match: () => true }, + addTypename: false, +}; + +export default (handlers = [], resolvers = {}, cacheOptions = {}) => { const cache = new InMemoryCache({ - fragmentMatcher, - addTypename: false, + ...defaultCacheOptions, + ...cacheOptions, }); const mockClient = createMockClient({ cache, resolvers }); diff --git a/spec/frontend/__helpers__/vue_test_utils_helper.js b/spec/frontend/__helpers__/vue_test_utils_helper.js index d6132ef84ac..a94cee84f74 100644 --- a/spec/frontend/__helpers__/vue_test_utils_helper.js +++ b/spec/frontend/__helpers__/vue_test_utils_helper.js @@ -1,4 +1,6 @@ -import { isArray } from 'lodash'; +import * as testingLibrary from '@testing-library/dom'; +import { createWrapper, WrapperArray, mount, shallowMount } from '@vue/test-utils'; +import { isArray, upperFirst } from 'lodash'; const vNodeContainsText = (vnode, text) => (vnode.text && vnode.text.includes(text)) || @@ -37,6 +39,17 @@ export const waitForMutation = (store, expectedMutationType) => }); export const extendedWrapper = (wrapper) => { + // https://testing-library.com/docs/queries/about + const AVAILABLE_QUERIES = [ + 'byRole', + 'byLabelText', + 'byPlaceholderText', + 'byText', + 'byDisplayValue', + 'byAltText', + 'byTitle', + ]; + if (isArray(wrapper) || !wrapper?.find) { // eslint-disable-next-line no-console console.warn( @@ -56,5 +69,63 @@ export const extendedWrapper = (wrapper) => { return this.findAll(`[data-testid="${id}"]`); }, }, + // `findBy` + ...AVAILABLE_QUERIES.reduce((accumulator, query) => { + return { + ...accumulator, + [`find${upperFirst(query)}`]: { + value(text, options = {}) { + const elements = testingLibrary[`queryAll${upperFirst(query)}`]( + wrapper.element, + text, + options, + ); + + // Return VTU `ErrorWrapper` if element is not found + // https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/src/error-wrapper.js + // VTU does not expose `ErrorWrapper` so, as of now, this is the best way to + // create an `ErrorWrapper` + if (!elements.length) { + const emptyElement = document.createElement('div'); + + return createWrapper(emptyElement).find('testing-library-element-not-found'); + } + + return createWrapper(elements[0], this.options || {}); + }, + }, + }; + }, {}), + // `findAllBy` + ...AVAILABLE_QUERIES.reduce((accumulator, query) => { + return { + ...accumulator, + [`findAll${upperFirst(query)}`]: { + value(text, options = {}) { + const elements = testingLibrary[`queryAll${upperFirst(query)}`]( + wrapper.element, + text, + options, + ); + + const wrappers = elements.map((element) => { + const elementWrapper = createWrapper(element, this.options || {}); + elementWrapper.selector = text; + + return elementWrapper; + }); + + const wrapperArray = new WrapperArray(wrappers); + wrapperArray.selector = text; + + return wrapperArray; + }, + }, + }; + }, {}), }); }; + +export const shallowMountExtended = (...args) => extendedWrapper(shallowMount(...args)); + +export const mountExtended = (...args) => extendedWrapper(mount(...args)); diff --git a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js index d4f8e36c169..dfe5a483223 100644 --- a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js +++ b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js @@ -1,7 +1,27 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper'; +import * as testingLibrary from '@testing-library/dom'; +import * as vtu from '@vue/test-utils'; +import { + shallowMount, + Wrapper as VTUWrapper, + WrapperArray as VTUWrapperArray, +} from '@vue/test-utils'; +import { + extendedWrapper, + shallowMountExtended, + mountExtended, + shallowWrapperContainsSlotText, +} from './vue_test_utils_helper'; + +jest.mock('@testing-library/dom', () => ({ + __esModule: true, + ...jest.requireActual('@testing-library/dom'), +})); describe('Vue test utils helpers', () => { + afterAll(() => { + jest.unmock('@testing-library/dom'); + }); + describe('shallowWrapperContainsSlotText', () => { const mockText = 'text'; const mockSlot = `
${mockText}
`; @@ -84,7 +104,7 @@ describe('Vue test utils helpers', () => { ); }); - it('should find the component by test id', () => { + it('should find the element by test id', () => { expect(mockComponent.findByTestId(testId).exists()).toBe(true); }); }); @@ -105,5 +125,187 @@ describe('Vue test utils helpers', () => { expect(mockComponent.findAllByTestId(testId)).toHaveLength(2); }); }); + + describe.each` + findMethod | expectedQuery + ${'findByRole'} | ${'queryAllByRole'} + ${'findByLabelText'} | ${'queryAllByLabelText'} + ${'findByPlaceholderText'} | ${'queryAllByPlaceholderText'} + ${'findByText'} | ${'queryAllByText'} + ${'findByDisplayValue'} | ${'queryAllByDisplayValue'} + ${'findByAltText'} | ${'queryAllByAltText'} + `('$findMethod', ({ findMethod, expectedQuery }) => { + const text = 'foo bar'; + const options = { selector: 'div' }; + const mockDiv = document.createElement('div'); + + let wrapper; + beforeEach(() => { + wrapper = extendedWrapper( + shallowMount({ + template: `
foo bar
`, + }), + ); + }); + + it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]); + + wrapper[findMethod](text, options); + + expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith( + wrapper.element, + text, + options, + ); + }); + + describe('when element is found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]); + jest.spyOn(vtu, 'createWrapper'); + }); + + it('returns a VTU wrapper', () => { + const result = wrapper[findMethod](text, options); + + expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options); + expect(result).toBeInstanceOf(VTUWrapper); + }); + }); + + describe('when multiple elements are found', () => { + beforeEach(() => { + const mockSpan = document.createElement('span'); + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv, mockSpan]); + jest.spyOn(vtu, 'createWrapper'); + }); + + it('returns the first element as a VTU wrapper', () => { + const result = wrapper[findMethod](text, options); + + expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options); + expect(result).toBeInstanceOf(VTUWrapper); + }); + }); + + describe('when element is not found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []); + }); + + it('returns a VTU error wrapper', () => { + expect(wrapper[findMethod](text, options).exists()).toBe(false); + }); + }); + }); + + describe.each` + findMethod | expectedQuery + ${'findAllByRole'} | ${'queryAllByRole'} + ${'findAllByLabelText'} | ${'queryAllByLabelText'} + ${'findAllByPlaceholderText'} | ${'queryAllByPlaceholderText'} + ${'findAllByText'} | ${'queryAllByText'} + ${'findAllByDisplayValue'} | ${'queryAllByDisplayValue'} + ${'findAllByAltText'} | ${'queryAllByAltText'} + `('$findMethod', ({ findMethod, expectedQuery }) => { + const text = 'foo bar'; + const options = { selector: 'div' }; + const mockElements = [ + document.createElement('li'), + document.createElement('li'), + document.createElement('li'), + ]; + + let wrapper; + beforeEach(() => { + wrapper = extendedWrapper( + shallowMount({ + template: ` + + `, + }), + ); + }); + + it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements); + + wrapper[findMethod](text, options); + + expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith( + wrapper.element, + text, + options, + ); + }); + + describe('when elements are found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements); + }); + + it('returns a VTU wrapper array', () => { + const result = wrapper[findMethod](text, options); + + expect(result).toBeInstanceOf(VTUWrapperArray); + expect( + result.wrappers.every( + (resultWrapper) => + resultWrapper instanceof VTUWrapper && resultWrapper.options === wrapper.options, + ), + ).toBe(true); + expect(result.length).toBe(3); + }); + }); + + describe('when elements are not found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []); + }); + + it('returns an empty VTU wrapper array', () => { + const result = wrapper[findMethod](text, options); + + expect(result).toBeInstanceOf(VTUWrapperArray); + expect(result.length).toBe(0); + }); + }); + }); + }); + + describe.each` + mountExtendedFunction | expectedMountFunction + ${shallowMountExtended} | ${'shallowMount'} + ${mountExtended} | ${'mount'} + `('$mountExtendedFunction', ({ mountExtendedFunction, expectedMountFunction }) => { + const FakeComponent = jest.fn(); + const options = { + propsData: { + foo: 'bar', + }, + }; + + beforeEach(() => { + const mockWrapper = { find: jest.fn() }; + jest.spyOn(vtu, expectedMountFunction).mockImplementation(() => mockWrapper); + }); + + it(`calls \`${expectedMountFunction}\` with passed arguments`, () => { + mountExtendedFunction(FakeComponent, options); + + expect(vtu[expectedMountFunction]).toHaveBeenCalledWith(FakeComponent, options); + }); + + it('returns extended wrapper', () => { + const result = mountExtendedFunction(FakeComponent, options); + + expect(result).toHaveProperty('find'); + expect(result).toHaveProperty('findByTestId'); + }); }); }); diff --git a/spec/frontend/__helpers__/web_worker_fake.js b/spec/frontend/__helpers__/web_worker_fake.js new file mode 100644 index 00000000000..041a9bd8540 --- /dev/null +++ b/spec/frontend/__helpers__/web_worker_fake.js @@ -0,0 +1,71 @@ +import path from 'path'; + +const isRelative = (pathArg) => pathArg.startsWith('.'); + +const transformRequirePath = (base, pathArg) => { + if (!isRelative(pathArg)) { + return pathArg; + } + + return path.resolve(base, pathArg); +}; + +const createRelativeRequire = (filename) => { + const rel = path.relative(__dirname, path.dirname(filename)); + const base = path.resolve(__dirname, rel); + + // reason: Dynamic require should be fine here since the code is dynamically evaluated anyways. + // eslint-disable-next-line import/no-dynamic-require, global-require + return (pathArg) => require(transformRequirePath(base, pathArg)); +}; + +/** + * Simulates a WebWorker module similar to the kind created by Webpack's [`worker-loader`][1] + * + * [1]: https://webpack.js.org/loaders/worker-loader/ + */ +export class FakeWebWorker { + /** + * Constructs a new FakeWebWorker instance + * + * @param {String} filename is the full path of the code, which is used to resolve relative imports. + * @param {String} code is the raw code of the web worker, which is dynamically evaluated on construction. + */ + constructor(filename, code) { + let isAlive = true; + + const clientTarget = new EventTarget(); + const workerTarget = new EventTarget(); + + this.addEventListener = (...args) => clientTarget.addEventListener(...args); + this.removeEventListener = (...args) => clientTarget.removeEventListener(...args); + this.postMessage = (message) => { + if (!isAlive) { + return; + } + + workerTarget.dispatchEvent(new MessageEvent('message', { data: message })); + }; + this.terminate = () => { + isAlive = false; + }; + + const workerScope = { + addEventListener: (...args) => workerTarget.addEventListener(...args), + removeEventListener: (...args) => workerTarget.removeEventListener(...args), + postMessage: (message) => { + if (!isAlive) { + return; + } + + clientTarget.dispatchEvent(new MessageEvent('message', { data: message })); + }, + }; + + // reason: `no-new-func` is like `eval` except it only executed on global scope and it's easy + // to pass in local references. `eval` is very unsafe in production, but in our test environment + // we shold be fine. + // eslint-disable-next-line no-new-func + Function('self', 'require', code)(workerScope, createRelativeRequire(filename)); + } +} diff --git a/spec/frontend/__helpers__/web_worker_mock.js b/spec/frontend/__helpers__/web_worker_mock.js deleted file mode 100644 index 2b4a391e1d2..00000000000 --- a/spec/frontend/__helpers__/web_worker_mock.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable class-methods-use-this */ -export default class WebWorkerMock { - addEventListener() {} - - removeEventListener() {} - - terminate() {} - - postMessage() {} -} diff --git a/spec/frontend/__helpers__/web_worker_transformer.js b/spec/frontend/__helpers__/web_worker_transformer.js new file mode 100644 index 00000000000..5b2f7d77947 --- /dev/null +++ b/spec/frontend/__helpers__/web_worker_transformer.js @@ -0,0 +1,18 @@ +/* eslint-disable import/no-commonjs */ +const babelJestTransformer = require('babel-jest'); + +// This Jest will transform the code of a WebWorker module into a FakeWebWorker subclass. +// This is meant to mirror Webpack's [`worker-loader`][1]. +// [1]: https://webpack.js.org/loaders/worker-loader/ +module.exports = { + process: (contentArg, filename, ...args) => { + const { code: content } = babelJestTransformer.process(contentArg, filename, ...args); + + return `const { FakeWebWorker } = require("helpers/web_worker_fake"); + module.exports = class JestTransformedWorker extends FakeWebWorker { + constructor() { + super(${JSON.stringify(filename)}, ${JSON.stringify(content)}); + } + };`; + }, +}; diff --git a/spec/frontend/__mocks__/vue/index.js b/spec/frontend/__mocks__/vue/index.js new file mode 100644 index 00000000000..52a5c6c5fcd --- /dev/null +++ b/spec/frontend/__mocks__/vue/index.js @@ -0,0 +1,7 @@ +import Vue from 'vue'; + +Vue.config.productionTip = false; +Vue.config.devtools = false; + +export default Vue; +export * from 'vue'; diff --git a/spec/frontend/access_tokens/index_spec.js b/spec/frontend/access_tokens/index_spec.js index e3f17e21739..1d8ac7cec25 100644 --- a/spec/frontend/access_tokens/index_spec.js +++ b/spec/frontend/access_tokens/index_spec.js @@ -25,18 +25,22 @@ describe('access tokens', () => { }); describe.each` - initFunction | mountSelector | expectedComponent - ${initExpiresAtField} | ${'js-access-tokens-expires-at'} | ${ExpiresAtField} - ${initProjectsField} | ${'js-access-tokens-projects'} | ${ProjectsField} - `('$initFunction', ({ initFunction, mountSelector, expectedComponent }) => { + initFunction | mountSelector | fieldName | expectedComponent + ${initExpiresAtField} | ${'js-access-tokens-expires-at'} | ${'expiresAt'} | ${ExpiresAtField} + ${initProjectsField} | ${'js-access-tokens-projects'} | ${'projects'} | ${ProjectsField} + `('$initFunction', ({ initFunction, mountSelector, fieldName, expectedComponent }) => { describe('when mount element exists', () => { + const nameAttribute = `access_tokens[${fieldName}]`; + const idAttribute = `access_tokens_${fieldName}`; + beforeEach(() => { const mountEl = document.createElement('div'); mountEl.classList.add(mountSelector); const input = document.createElement('input'); - input.setAttribute('name', 'foo-bar'); - input.setAttribute('id', 'foo-bar'); + input.setAttribute('name', nameAttribute); + input.setAttribute('data-js-name', fieldName); + input.setAttribute('id', idAttribute); input.setAttribute('placeholder', 'Foo bar'); input.setAttribute('value', '1,2'); @@ -57,8 +61,8 @@ describe('access tokens', () => { expect(component.exists()).toBe(true); expect(component.props('inputAttrs')).toEqual({ - name: 'foo-bar', - id: 'foo-bar', + name: nameAttribute, + id: idAttribute, value: '1,2', placeholder: 'Foo bar', }); diff --git a/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js new file mode 100644 index 00000000000..ae9b6f57ee0 --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js @@ -0,0 +1,66 @@ +import { GlFormCheckbox } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import SignupCheckbox from '~/pages/admin/application_settings/general/components/signup_checkbox.vue'; + +describe('Signup Form', () => { + let wrapper; + + const props = { + name: 'name', + helpText: 'some help text', + label: 'a label', + value: true, + dataQaSelector: 'qa_selector', + }; + + const mountComponent = () => { + wrapper = shallowMount(SignupCheckbox, { + propsData: props, + stubs: { + GlFormCheckbox, + }, + }); + }; + + const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`); + const findHiddenInput = () => findByTestId('input'); + const findCheckbox = () => wrapper.find(GlFormCheckbox); + const findCheckboxLabel = () => findByTestId('label'); + const findHelpText = () => findByTestId('helpText'); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Signup Checkbox', () => { + beforeEach(() => { + mountComponent(); + }); + + describe('hidden input element', () => { + it('gets passed correct values from props', () => { + expect(findHiddenInput().attributes('name')).toBe(props.name); + + expect(findHiddenInput().attributes('value')).toBe('1'); + }); + }); + + describe('checkbox', () => { + it('gets passed correct checked value', () => { + expect(findCheckbox().attributes('checked')).toBe('true'); + }); + + it('gets passed correct label', () => { + expect(findCheckboxLabel().text()).toBe(props.label); + }); + + it('gets passed correct help text', () => { + expect(findHelpText().text()).toBe(props.helpText); + }); + + it('gets passed data qa selector', () => { + expect(findCheckbox().attributes('data-qa-selector')).toBe(props.dataQaSelector); + }); + }); + }); +}); diff --git a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js new file mode 100644 index 00000000000..18339164d5a --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js @@ -0,0 +1,331 @@ +import { GlButton, GlModal } from '@gitlab/ui'; +import { within, fireEvent } from '@testing-library/dom'; +import { shallowMount, mount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import SignupForm from '~/pages/admin/application_settings/general/components/signup_form.vue'; +import { mockData } from '../mock_data'; + +jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); + +describe('Signup Form', () => { + let wrapper; + let formSubmitSpy; + + const mountComponent = ({ injectedProps = {}, mountFn = shallowMount, stubs = {} } = {}) => { + wrapper = extendedWrapper( + mountFn(SignupForm, { + provide: { + ...mockData, + ...injectedProps, + }, + stubs, + }), + ); + }; + + const queryByLabelText = (text) => within(wrapper.element).queryByLabelText(text); + + const findForm = () => wrapper.findByTestId('form'); + const findInputCsrf = () => findForm().find('[name="authenticity_token"]'); + const findFormSubmitButton = () => findForm().find(GlButton); + + const findDenyListRawRadio = () => queryByLabelText('Enter denylist manually'); + const findDenyListFileRadio = () => queryByLabelText('Upload denylist file'); + + const findDenyListRawInputGroup = () => wrapper.findByTestId('domain-denylist-raw-input-group'); + const findDenyListFileInputGroup = () => wrapper.findByTestId('domain-denylist-file-input-group'); + + const findRequireAdminApprovalCheckbox = () => + wrapper.findByTestId('require-admin-approval-checkbox'); + const findUserCapInput = () => wrapper.findByTestId('user-cap-input'); + const findModal = () => wrapper.find(GlModal); + + afterEach(() => { + wrapper.destroy(); + + formSubmitSpy = null; + }); + + describe('form data', () => { + beforeEach(() => { + mountComponent(); + }); + + it.each` + prop | propValue | elementSelector | formElementPassedDataType | formElementKey | expected + ${'signupEnabled'} | ${mockData.signupEnabled} | ${'[name="application_setting[signup_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.signupEnabled} + ${'requireAdminApprovalAfterUserSignup'} | ${mockData.requireAdminApprovalAfterUserSignup} | ${'[name="application_setting[require_admin_approval_after_user_signup]"]'} | ${'prop'} | ${'value'} | ${mockData.requireAdminApprovalAfterUserSignup} + ${'sendUserConfirmationEmail'} | ${mockData.sendUserConfirmationEmail} | ${'[name="application_setting[send_user_confirmation_email]"]'} | ${'prop'} | ${'value'} | ${mockData.sendUserConfirmationEmail} + ${'newUserSignupsCap'} | ${mockData.newUserSignupsCap} | ${'[name="application_setting[new_user_signups_cap]"]'} | ${'attribute'} | ${'value'} | ${mockData.newUserSignupsCap} + ${'minimumPasswordLength'} | ${mockData.minimumPasswordLength} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'value'} | ${mockData.minimumPasswordLength} + ${'minimumPasswordLengthMin'} | ${mockData.minimumPasswordLengthMin} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'min'} | ${mockData.minimumPasswordLengthMin} + ${'minimumPasswordLengthMax'} | ${mockData.minimumPasswordLengthMax} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'max'} | ${mockData.minimumPasswordLengthMax} + ${'domainAllowlistRaw'} | ${mockData.domainAllowlistRaw} | ${'[name="application_setting[domain_allowlist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainAllowlistRaw} + ${'domainDenylistEnabled'} | ${mockData.domainDenylistEnabled} | ${'[name="application_setting[domain_denylist_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.domainDenylistEnabled} + ${'denylistTypeRawSelected'} | ${mockData.denylistTypeRawSelected} | ${'[name="denylist_type"]'} | ${'attribute'} | ${'checked'} | ${'raw'} + ${'domainDenylistRaw'} | ${mockData.domainDenylistRaw} | ${'[name="application_setting[domain_denylist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainDenylistRaw} + ${'emailRestrictionsEnabled'} | ${mockData.emailRestrictionsEnabled} | ${'[name="application_setting[email_restrictions_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.emailRestrictionsEnabled} + ${'emailRestrictions'} | ${mockData.emailRestrictions} | ${'[name="application_setting[email_restrictions]"]'} | ${'value'} | ${'value'} | ${mockData.emailRestrictions} + ${'afterSignUpText'} | ${mockData.afterSignUpText} | ${'[name="application_setting[after_sign_up_text]"]'} | ${'value'} | ${'value'} | ${mockData.afterSignUpText} + `( + 'form element $elementSelector gets $expected value for $formElementKey $formElementPassedDataType when prop $prop is set to $propValue', + ({ elementSelector, expected, formElementKey, formElementPassedDataType }) => { + const formElement = wrapper.find(elementSelector); + + switch (formElementPassedDataType) { + case 'attribute': + expect(formElement.attributes(formElementKey)).toBe(expected); + break; + case 'prop': + expect(formElement.props(formElementKey)).toBe(expected); + break; + case 'value': + expect(formElement.element.value).toBe(expected); + break; + default: + expect(formElement.props(formElementKey)).toBe(expected); + break; + } + }, + ); + it('gets passed the path for action attribute', () => { + expect(findForm().attributes('action')).toBe(mockData.settingsPath); + }); + + it('gets passed the csrf token as a hidden input value', () => { + expect(findInputCsrf().attributes('type')).toBe('hidden'); + + expect(findInputCsrf().attributes('value')).toBe('mock-csrf-token'); + }); + }); + + describe('domain deny list', () => { + describe('when it is set to raw from props', () => { + beforeEach(() => { + mountComponent({ mountFn: mount }); + }); + + it('has raw list selected', () => { + expect(findDenyListRawRadio().checked).toBe(true); + }); + + it('has file not selected', () => { + expect(findDenyListFileRadio().checked).toBe(false); + }); + + it('raw list input is displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(true); + }); + + it('file input is not displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(false); + }); + + describe('when user clicks on file radio', () => { + beforeEach(() => { + fireEvent.click(findDenyListFileRadio()); + }); + + it('has raw list not selected', () => { + expect(findDenyListRawRadio().checked).toBe(false); + }); + + it('has file selected', () => { + expect(findDenyListFileRadio().checked).toBe(true); + }); + + it('raw list input is not displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(false); + }); + + it('file input is displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(true); + }); + }); + }); + + describe('when it is set to file from injected props', () => { + beforeEach(() => { + mountComponent({ mountFn: mount, injectedProps: { denylistTypeRawSelected: false } }); + }); + + it('has raw list not selected', () => { + expect(findDenyListRawRadio().checked).toBe(false); + }); + + it('has file selected', () => { + expect(findDenyListFileRadio().checked).toBe(true); + }); + + it('raw list input is not displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(false); + }); + + it('file input is displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(true); + }); + + describe('when user clicks on raw list radio', () => { + beforeEach(() => { + fireEvent.click(findDenyListRawRadio()); + }); + + it('has raw list selected', () => { + expect(findDenyListRawRadio().checked).toBe(true); + }); + + it('has file not selected', () => { + expect(findDenyListFileRadio().checked).toBe(false); + }); + + it('raw list input is displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(true); + }); + + it('file input is not displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(false); + }); + }); + }); + }); + + describe('form submit button confirmation modal for side-effect of adding possibly unwanted new users', () => { + it.each` + requireAdminApprovalAction | userCapAction | buttonEffect + ${'unchanged from true'} | ${'unchanged'} | ${'submits form'} + ${'unchanged from false'} | ${'unchanged'} | ${'submits form'} + ${'toggled off'} | ${'unchanged'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'unchanged'} | ${'submits form'} + ${'unchanged from false'} | ${'increased'} | ${'shows confirmation modal'} + ${'unchanged from true'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled off'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'decreased'} | ${'submits form'} + ${'unchanged from false'} | ${'changed from limited to unlimited'} | ${'shows confirmation modal'} + ${'unchanged from false'} | ${'changed from unlimited to limited'} | ${'submits form'} + ${'unchanged from false'} | ${'unchanged from unlimited'} | ${'submits form'} + `( + '$buttonEffect if require admin approval for new sign-ups is $requireAdminApprovalAction and the user cap is $userCapAction', + async ({ requireAdminApprovalAction, userCapAction, buttonEffect }) => { + let isModalDisplayed; + + switch (buttonEffect) { + case 'shows confirmation modal': + isModalDisplayed = true; + break; + case 'submits form': + isModalDisplayed = false; + break; + default: + isModalDisplayed = false; + break; + } + + const isFormSubmittedWhenClickingFormSubmitButton = !isModalDisplayed; + + const injectedProps = {}; + + const USER_CAP_DEFAULT = 5; + + switch (userCapAction) { + case 'changed from unlimited to limited': + injectedProps.newUserSignupsCap = ''; + break; + case 'unchanged from unlimited': + injectedProps.newUserSignupsCap = ''; + break; + default: + injectedProps.newUserSignupsCap = USER_CAP_DEFAULT; + break; + } + + switch (requireAdminApprovalAction) { + case 'unchanged from true': + injectedProps.requireAdminApprovalAfterUserSignup = true; + break; + case 'unchanged from false': + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + case 'toggled off': + injectedProps.requireAdminApprovalAfterUserSignup = true; + break; + case 'toggled on': + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + default: + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + } + + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + + await mountComponent({ + injectedProps, + stubs: { GlButton, GlModal: stubComponent(GlModal) }, + }); + + findModal().vm.show = jest.fn(); + + if ( + requireAdminApprovalAction === 'toggled off' || + requireAdminApprovalAction === 'toggled on' + ) { + await findRequireAdminApprovalCheckbox().vm.$emit('input', false); + } + + switch (userCapAction) { + case 'increased': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT + 1); + break; + case 'decreased': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT - 1); + break; + case 'changed from limited to unlimited': + await findUserCapInput().vm.$emit('input', ''); + break; + case 'changed from unlimited to limited': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT); + break; + default: + break; + } + + await findFormSubmitButton().trigger('click'); + + if (isFormSubmittedWhenClickingFormSubmitButton) { + expect(formSubmitSpy).toHaveBeenCalled(); + expect(findModal().vm.show).not.toHaveBeenCalled(); + } else { + expect(formSubmitSpy).not.toHaveBeenCalled(); + expect(findModal().vm.show).toHaveBeenCalled(); + } + }, + ); + + describe('modal actions', () => { + beforeEach(async () => { + const INITIAL_USER_CAP = 5; + + await mountComponent({ + injectedProps: { + newUserSignupsCap: INITIAL_USER_CAP, + }, + stubs: { GlButton, GlModal: stubComponent(GlModal) }, + }); + + await findUserCapInput().vm.$emit('input', INITIAL_USER_CAP + 1); + + await findFormSubmitButton().trigger('click'); + }); + + it('submits the form after clicking approve users button', async () => { + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + + await findModal().vm.$emit('primary'); + + expect(formSubmitSpy).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/admin/signup_restrictions/mock_data.js b/spec/frontend/admin/signup_restrictions/mock_data.js new file mode 100644 index 00000000000..624a5614c9c --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/mock_data.js @@ -0,0 +1,41 @@ +export const rawMockData = { + host: 'path/to/host', + settingsPath: 'path/to/settings', + signupEnabled: 'true', + requireAdminApprovalAfterUserSignup: 'true', + sendUserConfirmationEmail: 'true', + minimumPasswordLength: '8', + minimumPasswordLengthMin: '3', + minimumPasswordLengthMax: '10', + minimumPasswordLengthHelpLink: 'help/link', + domainAllowlistRaw: 'domain1.com, domain2.com', + newUserSignupsCap: '8', + domainDenylistEnabled: 'true', + denylistTypeRawSelected: 'true', + domainDenylistRaw: 'domain2.com, domain3.com', + emailRestrictionsEnabled: 'true', + supportedSyntaxLinkUrl: '/supported/syntax/link', + emailRestrictions: 'user1@domain.com, user2@domain.com', + afterSignUpText: 'Congratulations on your successful sign-up!', +}; + +export const mockData = { + host: 'path/to/host', + settingsPath: 'path/to/settings', + signupEnabled: true, + requireAdminApprovalAfterUserSignup: true, + sendUserConfirmationEmail: true, + minimumPasswordLength: '8', + minimumPasswordLengthMin: '3', + minimumPasswordLengthMax: '10', + minimumPasswordLengthHelpLink: 'help/link', + domainAllowlistRaw: 'domain1.com, domain2.com', + newUserSignupsCap: '8', + domainDenylistEnabled: true, + denylistTypeRawSelected: true, + domainDenylistRaw: 'domain2.com, domain3.com', + emailRestrictionsEnabled: true, + supportedSyntaxLinkUrl: '/supported/syntax/link', + emailRestrictions: 'user1@domain.com, user2@domain.com', + afterSignUpText: 'Congratulations on your successful sign-up!', +}; diff --git a/spec/frontend/admin/signup_restrictions/utils.js b/spec/frontend/admin/signup_restrictions/utils.js new file mode 100644 index 00000000000..30a95467e09 --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/utils.js @@ -0,0 +1,19 @@ +export const setDataAttributes = (data, element) => { + Object.keys(data).forEach((key) => { + const value = data[key]; + + // attribute should be: + // - valueless if value is 'true' + // - absent if value is 'false' + switch (value) { + case false: + break; + case true: + element.dataset[`${key}`] = ''; + break; + default: + element.dataset[`${key}`] = value; + break; + } + }); +}; diff --git a/spec/frontend/admin/signup_restrictions/utils_spec.js b/spec/frontend/admin/signup_restrictions/utils_spec.js new file mode 100644 index 00000000000..fd5c4c3317b --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/utils_spec.js @@ -0,0 +1,22 @@ +import { getParsedDataset } from '~/pages/admin/application_settings/utils'; +import { rawMockData, mockData } from './mock_data'; + +describe('utils', () => { + describe('getParsedDataset', () => { + it('returns correct results', () => { + expect( + getParsedDataset({ + dataset: rawMockData, + booleanAttributes: [ + 'signupEnabled', + 'requireAdminApprovalAfterUserSignup', + 'sendUserConfirmationEmail', + 'domainDenylistEnabled', + 'denylistTypeRawSelected', + 'emailRestrictionsEnabled', + ], + }), + ).toEqual(mockData); + }); + }); +}); diff --git a/spec/frontend/admin/users/components/user_date_spec.js b/spec/frontend/admin/users/components/user_date_spec.js index 6428b10059b..1a2f2938db5 100644 --- a/spec/frontend/admin/users/components/user_date_spec.js +++ b/spec/frontend/admin/users/components/user_date_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import UserDate from '~/admin/users/components/user_date.vue'; +import UserDate from '~/vue_shared/components/user_date.vue'; import { users } from '../mock_data'; const mockDate = users[0].createdAt; diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js index f1fcc20fb65..424b0deebd3 100644 --- a/spec/frontend/admin/users/components/users_table_spec.js +++ b/spec/frontend/admin/users/components/users_table_spec.js @@ -3,8 +3,8 @@ import { mount } from '@vue/test-utils'; import AdminUserActions from '~/admin/users/components/user_actions.vue'; import AdminUserAvatar from '~/admin/users/components/user_avatar.vue'; -import AdminUserDate from '~/admin/users/components/user_date.vue'; import AdminUsersTable from '~/admin/users/components/users_table.vue'; +import AdminUserDate from '~/vue_shared/components/user_date.vue'; import { users, paths } from '../mock_data'; diff --git a/spec/frontend/admin/users/new_spec.js b/spec/frontend/admin/users/new_spec.js new file mode 100644 index 00000000000..692c583dca8 --- /dev/null +++ b/spec/frontend/admin/users/new_spec.js @@ -0,0 +1,76 @@ +import { + setupInternalUserRegexHandler, + ID_USER_EMAIL, + ID_USER_EXTERNAL, + ID_WARNING, +} from '~/admin/users/new'; + +describe('admin/users/new', () => { + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; + + let elExternal; + let elUserEmail; + let elWarningMessage; + + beforeEach(() => { + loadFixtures(FIXTURE); + setupInternalUserRegexHandler(); + + elExternal = document.getElementById(ID_USER_EXTERNAL); + elUserEmail = document.getElementById(ID_USER_EMAIL); + elWarningMessage = document.getElementById(ID_WARNING); + + elExternal.checked = true; + }); + + const changeEmail = (val) => { + elUserEmail.value = val; + elUserEmail.dispatchEvent(new Event('input')); + }; + + const hasHiddenWarning = () => elWarningMessage.classList.contains('hidden'); + + describe('Behaviour of userExternal checkbox', () => { + it('hides warning by default', () => { + expect(hasHiddenWarning()).toBe(true); + }); + + describe('when matches email as internal', () => { + beforeEach(() => { + changeEmail('test@'); + }); + + it('has external unchecked', () => { + expect(elExternal.checked).toBe(false); + }); + + it('shows warning', () => { + expect(hasHiddenWarning()).toBe(false); + }); + + describe('when external is checked again', () => { + beforeEach(() => { + elExternal.dispatchEvent(new Event('change')); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); + + describe('when matches emails as external', () => { + beforeEach(() => { + changeEmail('test.ext@'); + }); + + it('has external checked', () => { + expect(elExternal.checked).toBe(true); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); +}); diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap deleted file mode 100644 index 1f8429af7dd..00000000000 --- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap +++ /dev/null @@ -1,524 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AlertsSettingsForm with default values renders the initial template 1`] = ` -
-
- -
- -
-
- -
-
- -
- - - - - - -
-
- -
- - - - - - - -
- -
- - - -
-
-
- - - - - - -