diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index e65c18c07a946d3fa992b068bc8404b403392a9b..7eeb0c852e50500770eb5828b00bc9b6ba038244 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -7,7 +7,7 @@ import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initFilePickers from '~/file_pickers';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
-import initProjectRemoveModal from '~/projects/project_remove_modal';
+import initProjectDeleteButton from '~/projects/project_delete_button';
import UserCallout from '~/user_callout';
import initServiceDesk from '~/projects/settings_service_desk';
@@ -15,7 +15,7 @@ document.addEventListener('DOMContentLoaded', () => {
initFilePickers();
initConfirmDangerModal();
initSettingsPanels();
- initProjectRemoveModal();
+ initProjectDeleteButton();
mountBadgeSettings(PROJECT_BADGE);
new UserCallout({ className: 'js-service-desk-callout' }); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/projects/components/project_delete_button.vue b/app/assets/javascripts/projects/components/project_delete_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4b27c5e3d30bf9a3d8ca7e4ccac77f577fb87bff
--- /dev/null
+++ b/app/assets/javascripts/projects/components/project_delete_button.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+ {{ $options.strings.modalBody }}
+
+
+
diff --git a/app/assets/javascripts/projects/components/remove_modal.vue b/app/assets/javascripts/projects/components/remove_modal.vue
deleted file mode 100644
index 37f58efcb307aa35875fac1222d166b31faf2d45..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/projects/components/remove_modal.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b00edc94abd1143f1cf57d6c626e89347fa2d1fa
--- /dev/null
+++ b/app/assets/javascripts/projects/components/shared/delete_button.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/projects/project_remove_modal.js b/app/assets/javascripts/projects/project_delete_button.js
similarity index 50%
rename from app/assets/javascripts/projects/project_remove_modal.js
rename to app/assets/javascripts/projects/project_delete_button.js
index dbdad1bf6f176abbbee783e3403a7628bff42c74..aa7fc31d307cb62cc72710ccbbd76e0ca0460d7f 100644
--- a/app/assets/javascripts/projects/project_remove_modal.js
+++ b/app/assets/javascripts/projects/project_delete_button.js
@@ -1,21 +1,20 @@
import Vue from 'vue';
-import RemoveProjectModal from './components/remove_modal.vue';
+import ProjectDeleteButton from './components/project_delete_button.vue';
-export default (selector = '#js-confirm-project-remove') => {
+export default (selector = '#js-project-delete-button') => {
const el = document.querySelector(selector);
if (!el) return;
- const { formPath, confirmPhrase, warningMessage } = el.dataset;
+ const { confirmPhrase, formPath } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
render(createElement) {
- return createElement(RemoveProjectModal, {
+ return createElement(ProjectDeleteButton, {
props: {
confirmPhrase,
- warningMessage,
formPath,
},
});
diff --git a/app/views/projects/_remove.html.haml b/app/views/projects/_remove.html.haml
index 528d802261c072bfd65911d1e278fd584e78db4f..e85f74d094fbac2db442d84717a8c5fecda1d5bb 100644
--- a/app/views/projects/_remove.html.haml
+++ b/app/views/projects/_remove.html.haml
@@ -6,4 +6,4 @@
%strong= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.')
%p
%strong= _('Removed projects cannot be restored!')
- #js-confirm-project-remove{ data: { form_path: project_path(project), confirm_phrase: project.path, warning_message: remove_project_message(project) } }
+ #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } }
diff --git a/changelogs/unreleased/36596-update-project-delete-modal.yml b/changelogs/unreleased/36596-update-project-delete-modal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..93ba3e58b8a496fece6a64c92aa8de4c3adc6018
--- /dev/null
+++ b/changelogs/unreleased/36596-update-project-delete-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Update project remove modal to add additional warnings
+merge_request: 36962
+author:
+type: changed
diff --git a/ee/app/assets/javascripts/pages/projects/edit/index.js b/ee/app/assets/javascripts/pages/projects/edit/index.js
index 0056e4341c3ecabeed98cb71b5bf7f4a3ca6effb..1b8b702836697e1de3a549496524633c42771765 100644
--- a/ee/app/assets/javascripts/pages/projects/edit/index.js
+++ b/ee/app/assets/javascripts/pages/projects/edit/index.js
@@ -5,6 +5,7 @@ import mountApprovals from 'ee/approvals/mount_project_settings';
import UsersSelect from '~/users_select';
import UserCallout from '~/user_callout';
import groupsSelect from '~/groups_select';
+import initProjectAdjournedDeleteButton from 'ee/projects/project_adjourned_delete_button';
document.addEventListener('DOMContentLoaded', () => {
new UsersSelect();
@@ -13,4 +14,6 @@ document.addEventListener('DOMContentLoaded', () => {
new UserCallout({ className: 'js-mr-approval-callout' });
mountApprovals(document.getElementById('js-mr-approvals-settings'));
+
+ initProjectAdjournedDeleteButton();
});
diff --git a/ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue b/ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..df1f3321258416f2a9e5bbae50e7606cf78e7827
--- /dev/null
+++ b/ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+ {{ $options.strings.modalBody }}
+
+
+
+
+
+ {{ adjournedRemovalDate }}
+
+
+
+
+
+
+
+
+
diff --git a/ee/app/assets/javascripts/projects/project_adjourned_delete_button.js b/ee/app/assets/javascripts/projects/project_adjourned_delete_button.js
new file mode 100644
index 0000000000000000000000000000000000000000..d90061daedeafcd3b35e08731fbee0ce47464ef9
--- /dev/null
+++ b/ee/app/assets/javascripts/projects/project_adjourned_delete_button.js
@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import ProjectAdjournedDeleteButton from './components/project_adjourned_delete_button.vue';
+
+export default (selector = '#js-project-adjourned-delete-button') => {
+ const el = document.querySelector(selector);
+
+ if (!el) return;
+
+ const { adjournedRemovalDate, confirmPhrase, formPath, recoveryHelpPath } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(ProjectAdjournedDeleteButton, {
+ props: {
+ adjournedRemovalDate,
+ confirmPhrase,
+ formPath,
+ recoveryHelpPath,
+ },
+ });
+ },
+ });
+};
diff --git a/ee/app/helpers/ee/projects_helper.rb b/ee/app/helpers/ee/projects_helper.rb
index 95abc57a2e96877502c785079a7589f447e4dad6..e2e16607f2c6d2f881b7fb91aad85f1ae9b4e06a 100644
--- a/ee/app/helpers/ee/projects_helper.rb
+++ b/ee/app/helpers/ee/projects_helper.rb
@@ -94,6 +94,17 @@ def remove_project_message(project)
{ date: date }
end
+ def permanent_delete_message(project)
+ message = _('This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all content: issues, merge requests, etc.')
+ html_escape(message) % remove_message_data(project)
+ end
+
+ def marked_for_removal_message(project)
+ date = permanent_deletion_date(Time.now.utc)
+ message = _('This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, including its repositories and all content: issues, merge requests, etc.')
+ html_escape(message) % remove_message_data(project).merge(date: date)
+ end
+
def permanent_deletion_date(date)
(date + ::Gitlab::CurrentSettings.deletion_adjourned_period.days).strftime('%F')
end
@@ -282,5 +293,15 @@ def get_project_security_nav_tabs(project, current_user)
nav_tabs
end
+
+ def remove_message_data(project)
+ {
+ project: project.path,
+ strongOpen: ''.html_safe,
+ strongClose: ''.html_safe,
+ codeOpen: ''.html_safe,
+ codeClose: ''.html_safe
+ }
+ end
end
end
diff --git a/ee/app/views/projects/_remove.html.haml b/ee/app/views/projects/_remove.html.haml
index 74f3f4aba90c1d55b989ad1e7295cfbb7dba93a3..cdbdc476506a45b41f81f51b63546a5bc98103ee 100644
--- a/ee/app/views/projects/_remove.html.haml
+++ b/ee/app/views/projects/_remove.html.haml
@@ -1,5 +1,8 @@
- return unless can?(current_user, :remove_project, project)
- adjourned_deletion = project.adjourned_deletion?
+- adjourned_date = adjourned_deletion ? permanent_deletion_date(Time.now.utc).to_s : nil;
+- admin_help_path = help_page_path('user/admin_area/settings/visibility_and_access_controls', anchor: 'default-deletion-adjourned-period-premium-only')
+- recovery_help_path = help_page_path('user/project/settings/index', anchor: 'remove-a-project')
- unless project.marked_for_deletion?
.sub-section
@@ -8,14 +11,15 @@
%strong= s_('Delayed Project Deletion (%{adjourned_deletion})') % { adjourned_deletion: adjourned_deletion ? 'Enabled' : 'Disabled' }
- if adjourned_deletion
= render 'projects/settings/marked_for_removal'
+ #js-project-adjourned-delete-button{ data: { recovery_help_path: recovery_help_path, adjourned_removal_date: adjourned_date, form_path: project_path(project), confirm_phrase: project.path } }
- else
%p
- = _("Removing a project deletes it immediately, there will be no delay before the project is permanently removed.")
- %p
- %strong= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.')
- %p
- %strong= _('Removed projects cannot be restored!')
- #js-confirm-project-remove{ data: { form_path: project_path(project), confirm_phrase: project.path, warning_message: remove_project_message(project) } }
+ %span.gl-text-gray-500= _('Projects will be permanently deleted immediately.')
+ = link_to(_('Customizable by an administrator.'), admin_help_path)
+ %p= permanent_delete_message(project)
+ %p
+ %strong= _('Are you ABSOLUTELY SURE you wish to delete this project?')
+ #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } }
- else
= render 'projects/settings/restore', project: project
diff --git a/ee/app/views/projects/settings/_marked_for_removal.html.haml b/ee/app/views/projects/settings/_marked_for_removal.html.haml
index 067fa5edb03696553eff64e5a003b930707f4433..b9a0521fc76e2d4203eafa6c501438edaefe4c5b 100644
--- a/ee/app/views/projects/settings/_marked_for_removal.html.haml
+++ b/ee/app/views/projects/settings/_marked_for_removal.html.haml
@@ -1,8 +1,6 @@
- return unless @project.feature_available?(:adjourned_deletion_for_projects_and_groups)
-- date = permanent_deletion_date(Time.now.utc)
-
%p
- = _("Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed.") %{ date: date }
- %br
- = _("Until that time, the project can be restored.")
+ %span.gl-text-gray-500= _('Projects will be permanently deleted after a 7-day waiting period.')
+ = link_to(_('Customizable by an administrator.'), help_page_path('user/admin_area/settings/visibility_and_access_controls', anchor: 'default-deletion-adjourned-period-premium-only'))
+%p= marked_for_removal_message(@project)
diff --git a/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap b/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..33f4e4350acd801755f97470011baf1e31e5e413
--- /dev/null
+++ b/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Project remove modal initialized matches the snapshot 1`] = `
+
+`;
diff --git a/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js b/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..57870387e037110aa2ee0520d17b6833d81ded40
--- /dev/null
+++ b/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js
@@ -0,0 +1,52 @@
+import { shallowMount } from '@vue/test-utils';
+import ProjectAdjournedDeleteButton from 'ee/projects/components/project_adjourned_delete_button.vue';
+import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
+
+jest.mock('lodash/uniqueId', () => () => 'fakeUniqueId');
+
+describe('Project remove modal', () => {
+ let wrapper;
+
+ const findSharedDeleteButton = () => wrapper.find(SharedDeleteButton);
+
+ const defaultProps = {
+ adjournedRemovalDate: '2020-12-12',
+ confirmPhrase: 'foo',
+ formPath: 'some/path',
+ recoveryHelpPath: 'recovery/help/path',
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ProjectAdjournedDeleteButton, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ SharedDeleteButton,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('initialized', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('passes confirmPhrase and formPath props to the shared delete button', () => {
+ expect(findSharedDeleteButton().props()).toEqual({
+ confirmPhrase: defaultProps.confirmPhrase,
+ formPath: defaultProps.formPath,
+ });
+ });
+ });
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f66c000e58ff546bce6bc62519420aeab495bc06..fe3ece1a65c7ba1b0615b54576d0e340b837278d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3083,6 +3083,9 @@ msgstr ""
msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}"
msgstr ""
+msgid "Are you ABSOLUTELY SURE you wish to delete this project?"
+msgstr ""
+
msgid "Are you setting up GitLab for a company?"
msgstr ""
@@ -4245,6 +4248,9 @@ msgstr ""
msgid "Cancel this job"
msgstr ""
+msgid "Cancel, keep project"
+msgstr ""
+
msgid "Canceled deployment to"
msgstr ""
@@ -7283,6 +7289,9 @@ msgstr ""
msgid "Customer Portal"
msgstr ""
+msgid "Customizable by an administrator."
+msgstr ""
+
msgid "Customize colors"
msgstr ""
@@ -7729,6 +7738,9 @@ msgstr ""
msgid "Delete project"
msgstr ""
+msgid "Delete project. Are you ABSOLUTELY SURE?"
+msgstr ""
+
msgid "Delete serverless domain?"
msgstr ""
@@ -16547,6 +16559,12 @@ msgstr ""
msgid "OnDemandScans|Target URL"
msgstr ""
+msgid "Once a project is permanently deleted it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its respositories and %{strongStart}all related resources%{strongEnd} including issues, merge requests etc."
+msgstr ""
+
+msgid "Once a project is permanently deleted it cannot be recovered. You will lose this project's repository and all content: issues, merge requests etc."
+msgstr ""
+
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr ""
@@ -17773,6 +17791,9 @@ msgstr ""
msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
msgstr ""
+msgid "Please type the following to confirm:"
+msgstr ""
+
msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately."
msgstr ""
@@ -18991,6 +19012,12 @@ msgstr ""
msgid "Projects to index"
msgstr ""
+msgid "Projects will be permanently deleted after a 7-day waiting period."
+msgstr ""
+
+msgid "Projects will be permanently deleted immediately."
+msgstr ""
+
msgid "Projects with critical vulnerabilities"
msgstr ""
@@ -19675,6 +19702,9 @@ msgstr ""
msgid "Recover hidden stage"
msgstr ""
+msgid "Recovering projects"
+msgstr ""
+
msgid "Recovery Codes"
msgstr ""
@@ -20069,12 +20099,6 @@ msgstr ""
msgid "Removes time estimate."
msgstr ""
-msgid "Removing a project deletes it immediately, there will be no delay before the project is permanently removed."
-msgstr ""
-
-msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed."
-msgstr ""
-
msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed. Are you ABSOLUTELY sure?"
msgstr ""
@@ -24392,6 +24416,15 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This action cannot be undone. You will lose the project's respository and all conent: issues, merge requests, etc."
+msgstr ""
+
+msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all content: issues, merge requests, etc."
+msgstr ""
+
+msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, including its repositories and all content: issues, merge requests, etc."
+msgstr ""
+
msgid "This also resolves all related threads"
msgstr ""
@@ -25851,9 +25884,6 @@ msgstr ""
msgid "Until"
msgstr ""
-msgid "Until that time, the project can be restored."
-msgstr ""
-
msgid "Unverified"
msgstr ""
@@ -27315,6 +27345,9 @@ msgstr ""
msgid "Yes, close issue"
msgstr ""
+msgid "Yes, delete project"
+msgstr ""
+
msgid "Yes, let me map Google Code users to full names or GitLab users."
msgstr ""
@@ -27330,6 +27363,9 @@ msgstr ""
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
msgstr ""
+msgid "You are about to permanently delete this project"
+msgstr ""
+
msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
msgstr ""
@@ -27480,6 +27516,9 @@ msgstr ""
msgid "You can only upload one design when dropping onto an existing design."
msgstr ""
+msgid "You can recover this project until %{date}"
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index ab0b672549143987ca4554f3edf70a325b8e15e1..4ad80b44b4db66338b3bd88987d629d3e1831295 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -260,7 +260,7 @@
end
it 'removes a project', :sidekiq_might_not_need_inline do
- expect { remove_with_confirm('Remove project', project.path) }.to change { Project.count }.by(-1)
+ expect { remove_with_confirm('Remove project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1)
expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
expect(Project.all.count).to be_zero
expect(project.issues).to be_empty
@@ -386,9 +386,9 @@
{ form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
end
- def remove_with_confirm(button_text, confirm_with)
+ def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confirm')
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
- click_button 'Confirm'
+ click_button confirm_button_text
end
end
diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..7d6022cc1b7bbf4a5667a0c0a58cb71e0a1ec524
--- /dev/null
+++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
@@ -0,0 +1,83 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Project remove modal initialized matches the snapshot 1`] = `
+
+`;
diff --git a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap
deleted file mode 100644
index 4d5b6c56a34da5f389c71a68947fbce376ba886a..0000000000000000000000000000000000000000
--- a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap
+++ /dev/null
@@ -1,126 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Project remove modal initialized matches the snapshot 1`] = `
-
-`;
diff --git a/spec/frontend/projects/components/project_delete_button_spec.js b/spec/frontend/projects/components/project_delete_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..444e465ebaa3c9468280714c80afc65b9be25224
--- /dev/null
+++ b/spec/frontend/projects/components/project_delete_button_spec.js
@@ -0,0 +1,47 @@
+import { shallowMount } from '@vue/test-utils';
+import ProjectDeleteButton from '~/projects/components/project_delete_button.vue';
+import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
+
+jest.mock('lodash/uniqueId', () => () => 'fakeUniqueId');
+
+describe('Project remove modal', () => {
+ let wrapper;
+
+ const findSharedDeleteButton = () => wrapper.find(SharedDeleteButton);
+
+ const defaultProps = {
+ confirmPhrase: 'foo',
+ formPath: 'some/path',
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ProjectDeleteButton, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ SharedDeleteButton,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('initialized', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('passes confirmPhrase and formPath props to the shared delete button', () => {
+ expect(findSharedDeleteButton().props()).toEqual(defaultProps);
+ });
+ });
+});
diff --git a/spec/frontend/projects/components/remove_modal_spec.js b/spec/frontend/projects/components/remove_modal_spec.js
deleted file mode 100644
index 339aee65b991933ae5851bf5af198aa03d99a982..0000000000000000000000000000000000000000
--- a/spec/frontend/projects/components/remove_modal_spec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlButton, GlModal } from '@gitlab/ui';
-import ProjectRemoveModal from '~/projects/components/remove_modal.vue';
-
-describe('Project remove modal', () => {
- let wrapper;
-
- const findFormElement = () => wrapper.find('form').element;
- const findConfirmButton = () => wrapper.find(GlModal).find(GlButton);
-
- const defaultProps = {
- formPath: 'some/path',
- confirmPhrase: 'foo',
- warningMessage: 'This can lead to data loss.',
- };
-
- const createComponent = (data = {}) => {
- wrapper = shallowMount(ProjectRemoveModal, {
- propsData: defaultProps,
- data: () => data,
- stubs: {
- GlButton,
- GlModal,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('initialized', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('matches the snapshot', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- describe('user input matches the confirmPhrase', () => {
- beforeEach(() => {
- createComponent({ userInput: defaultProps.confirmPhrase });
- });
-
- it('the confirm button is not dislabled', () => {
- expect(findConfirmButton().attributes('disabled')).toBe(undefined);
- });
-
- describe('and when the confirmation button is clicked', () => {
- beforeEach(() => {
- findConfirmButton().vm.$emit('click');
- });
-
- it('submits the form element', () => {
- expect(findFormElement().submit).toHaveBeenCalled();
- });
- });
- });
-});
diff --git a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..28e534ae1792513a8e345b664f028f7f84054c33
--- /dev/null
+++ b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Project remove modal intialized matches the snapshot 1`] = `
+
+`;
diff --git a/spec/frontend/projects/components/shared/delete_button_spec.js b/spec/frontend/projects/components/shared/delete_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6394a50011a86202fdd44b3b77cd170f669543d
--- /dev/null
+++ b/spec/frontend/projects/components/shared/delete_button_spec.js
@@ -0,0 +1,83 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
+
+jest.mock('~/lib/utils/csrf', () => ({ token: 'test-csrf-token' }));
+
+describe('Project remove modal', () => {
+ let wrapper;
+
+ 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 defaultProps = {
+ confirmPhrase: 'foo',
+ formPath: 'some/path',
+ };
+
+ const createComponent = (data = {}) => {
+ wrapper = shallowMount(SharedDeleteButton, {
+ propsData: defaultProps,
+ data: () => data,
+ stubs: {
+ GlModal,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('intialized', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('sets a csrf token on the authenticity form input', () => {
+ expect(findAuthenticityTokenInput().element.value).toEqual('test-csrf-token');
+ });
+
+ it('sets the form action to the provided path', () => {
+ expect(findFormElement().attributes('action')).toEqual(defaultProps.formPath);
+ });
+ });
+
+ describe('when the user input does not match the confirmPhrase', () => {
+ beforeEach(() => {
+ createComponent({ userInput: 'bar' });
+ });
+
+ it('the confirm button is disabled', () => {
+ expect(findConfirmButton().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('when the user input matches the confirmPhrase', () => {
+ beforeEach(() => {
+ createComponent({ userInput: defaultProps.confirmPhrase });
+ });
+
+ it('the confirm button is not disabled', () => {
+ expect(findConfirmButton().attributes('disabled')).toBe(undefined);
+ });
+ });
+
+ describe('when the modal is confirmed', () => {
+ beforeEach(() => {
+ createComponent();
+ findModal().vm.$emit('ok');
+ });
+
+ it('submits the form element', () => {
+ expect(findFormElement().element.submit).toHaveBeenCalled();
+ });
+ });
+});