diff --git a/app/components/projects/new_component.html.erb b/app/components/projects/new_component.html.erb new file mode 100644 index 00000000000..d5f4d2d6eda --- /dev/null +++ b/app/components/projects/new_component.html.erb @@ -0,0 +1,69 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + +<%= + component_wrapper( + tag: "turbo-frame", + refresh: :morph, + autoscroll: true, + data: { + turbo_action: :replace, + autoscroll_block: :start, + autoscroll_behavior: :smooth + } + ) do + settings_primer_form_with(model: project) do |f| + flex_layout do |container| + container.with_row(mb: 3) do + render( + Primer::Forms::FormList.new( + Projects::Settings::NameForm.new(f), + Projects::Settings::RelationsForm.new(f), + Projects::Settings::CustomFieldsForm.new(f) + ) + ) + end + + if template + container.with_row(mb: 3) do + render(Primer::Box.new(border: true, border_radius: 2, bg: :inset, p: 3)) do + render( + Projects::TemplateForm.new(f, template:, copy_options:) + ) + end + end + end + + container.with_row(mb: 3) do + render Projects::SubmitOrCancel.new(f) + end + end + end + end +%> diff --git a/app/components/projects/new_component.rb b/app/components/projects/new_component.rb new file mode 100644 index 00000000000..1b2cf4d418c --- /dev/null +++ b/app/components/projects/new_component.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Projects + class NewComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + options :project, :template, :copy_options + end +end diff --git a/app/components/projects/template_select_component.html.erb b/app/components/projects/template_select_component.html.erb new file mode 100644 index 00000000000..f4522173ecd --- /dev/null +++ b/app/components/projects/template_select_component.html.erb @@ -0,0 +1,59 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + +<%= + component_wrapper do + settings_primer_form_with( + url: new_project_path, + method: :get, + data: { + turbo_frame: "projects-new-component", + controller: "auto-submit", + auto_submit_delay_value: 0 + } + ) do |f| + render( + Primer::Box.new( + border: true, + border_radius: 2, + bg: :inset, + my: 3, + p: 3, + data: { + controller: "highlight-when-value-selected", + highlight_when_value_selected_highlight_class: resolve_css_classes(bg: :accent), + highlight_when_value_selected_target: "content" + } + ) + ) do + render Projects::TemplateAutocompleter.new(f, template_id:, parent_id:) + end + end + end +%> diff --git a/app/components/projects/template_select_component.rb b/app/components/projects/template_select_component.rb new file mode 100644 index 00000000000..389bb9d9349 --- /dev/null +++ b/app/components/projects/template_select_component.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Projects + class TemplateSelectComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + options :template, :parent + + def resolve_css_classes(**) + Primer::Classify.(**)[:class] + end + + private + + def template_id = template&.id + def parent_id = parent&.id + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7e518aa26ae..f4d0d7a23e8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -32,11 +32,13 @@ class ProjectsController < ApplicationController menu_item :overview menu_item :roadmap, only: :roadmap - before_action :find_project, except: %i[index new export_list_modal] + before_action :find_project, except: %i[index new create export_list_modal] before_action :load_query_or_deny_access, only: %i[index export_list_modal] before_action :authorize, only: %i[copy deactivate_work_package_attachments] - before_action :authorize_global, only: %i[new] + before_action :authorize_global, only: %i[new create] before_action :require_admin, only: %i[destroy destroy_info] + before_action :find_optional_template, only: %i[new create] + before_action :find_optional_parent, only: :new no_authorization_required! :index, :export_list_modal @@ -89,7 +91,19 @@ class ProjectsController < ApplicationController end def new - render layout: "no_menu" + if from_template? + new_from_template + else + new_blank + end + end + + def create + if from_template? + create_from_template + else + create_blank + end end def copy @@ -135,6 +149,68 @@ class ProjectsController < ApplicationController private + def from_template? = @template.present? + + def new_blank + @new_project = @parent&.children&.build || Project.new + + render layout: "no_menu" + end + + def new_from_template + @copy_options = Projects::CopyOptions.new + @new_project = Projects::CopyService + .new(user: current_user, source: @template, contract_options: { validate_model: false }) + .call(target_project_params: params.permit(:parent_id).to_h, attributes_only: true) + .result + + render layout: "no_menu" + end + + def create_blank + service_call = Projects::CreateService + .new(user: current_user) + .call(permitted_params.project) + + @new_project = service_call.result + + if service_call.success? + redirect_to project_path(@new_project), notice: I18n.t(:notice_successful_create) + else + flash.now[:error] = I18n.t(:notice_unsuccessful_create_with_reason, reason: service_call.message) + render action: :new, status: :unprocessable_entity + end + end + + def create_from_template # rubocop:disable Metrics/AbcSize + @copy_options = Projects::CopyOptions.new(copy_options_params) + + service_call = Projects::EnqueueCopyService + .new(user: current_user, model: @template) + .call( + target_project_params: permitted_params.project.to_h, + only: @copy_options.dependencies, + send_notifications: @copy_options.send_notifications + ) + + if service_call.success? + job = service_call.result + redirect_to job_status_path(job.job_id) + else + @new_project = service_call.result + flash.now[:error] = I18n.t(:notice_unsuccessful_create_with_reason, reason: service_call.message) + render action: :new, status: :unprocessable_entity + end + end + + def find_optional_template + @template = Project.templated.visible(current_user).find(params[:template_id]) if params[:template_id].present? + end + + def find_optional_parent + @parent = Project.visible(current_user).find(params[:parent_id]) if params[:parent_id].present? + end + def has_managed_project_folders?(project) project.project_storages.any?(&:project_folder_automatic?) end @@ -162,5 +238,9 @@ class ProjectsController < ApplicationController ::Exports::Register.list_formats(Project).map(&:to_s) end + def copy_options_params + params.expect(copy_options: [[dependencies: []], :send_notifications]) + end + helper_method :supported_export_formats end diff --git a/app/forms/projects/copy_options.rb b/app/forms/projects/copy_options.rb new file mode 100644 index 00000000000..b719f68ea4c --- /dev/null +++ b/app/forms/projects/copy_options.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Projects + class CopyOptions + include ActiveModel::Model + include ActiveModel::Attributes + + def self.all_dependencies + CopyService.copyable_dependencies.to_h { [it[:identifier], it[:name_source].call] } + end + + attribute :dependencies, array: true, default: all_dependencies.keys + attribute :send_notifications, :boolean, default: false + + validates :dependencies, inclusion: { in: all_dependencies.keys } + end +end diff --git a/app/forms/projects/copy_options_form.rb b/app/forms/projects/copy_options_form.rb new file mode 100644 index 00000000000..32cb33bbbce --- /dev/null +++ b/app/forms/projects/copy_options_form.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Projects + class CopyOptionsForm < ApplicationForm + form do |f| + f.check_box_group(name: :dependencies, label: I18n.t("js.project.copy.copy_options")) do |group| + CopyOptions.all_dependencies.each do |value, label| + group.check_box label:, value: + end + end + + f.separator + + f.check_box name: :send_notifications, label: I18n.t("label_project_copy_notifications") + end + end +end diff --git a/app/forms/projects/settings/custom_fields_form.rb b/app/forms/projects/settings/custom_fields_form.rb new file mode 100644 index 00000000000..2fffeb5e3e5 --- /dev/null +++ b/app/forms/projects/settings/custom_fields_form.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +module Projects + module Settings + class CustomFieldsForm < ApplicationForm + include ::CustomFields::CustomFieldRendering + + form do |f| + render_custom_fields(form: f) + end + + def additional_custom_field_input_arguments + { wrapper_id: nil } + end + + private + + def custom_fields + @custom_fields ||= model + .available_custom_fields + .required + end + end + end +end diff --git a/app/forms/projects/settings/relations_form.rb b/app/forms/projects/settings/relations_form.rb index 3837aa015bb..ced943e30c8 100644 --- a/app/forms/projects/settings/relations_form.rb +++ b/app/forms/projects/settings/relations_form.rb @@ -40,7 +40,7 @@ module Projects model: project_autocompleter_model, focusDirectly: false, dropdownPosition: "bottom", - url: ::API::V3::Utilities::PathHelper::ApiV3Path.projects_available_parents + "?of=#{model.id}", + url: project_autocompleter_url, filters: [], data: { qa_field_name: "parent" } } @@ -55,6 +55,12 @@ module Projects { id: parent.id, name: parent.name } end + + def project_autocompleter_url + url_str = ::API::V3::Utilities::PathHelper::ApiV3Path.projects_available_parents + url_str << "?of=#{model.id}" unless model.new_record? + url_str + end end end end diff --git a/app/forms/projects/submit_or_cancel.rb b/app/forms/projects/submit_or_cancel.rb new file mode 100644 index 00000000000..23d664f5635 --- /dev/null +++ b/app/forms/projects/submit_or_cancel.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Projects + class SubmitOrCancel < ApplicationForm + attr_reader :submit_options, :cancel_options + + form do |buttons| + buttons.group(layout: :horizontal) do |button_group| + button_group.submit(**submit_options) + button_group.button(**cancel_options) + end + end + + def initialize(submit_options: {}, cancel_options: {}) + super() + + @submit_options = submit_options.with_defaults(default_submit_options) + @cancel_options = cancel_options.with_defaults(default_cancel_options) + end + + private + + def default_submit_options + { + name: :submit, + scheme: :primary, + label: I18n.t("button_create"), + disabled: false + } + end + + def default_cancel_options + { + name: :cancel, + scheme: :default, + tag: :a, + href: OpenProject::StaticRouting::StaticRouter.new.url_helpers.projects_path, + label: I18n.t("button_cancel") + } + end + end +end diff --git a/app/forms/projects/template_autocompleter.rb b/app/forms/projects/template_autocompleter.rb new file mode 100644 index 00000000000..0c1d5d41083 --- /dev/null +++ b/app/forms/projects/template_autocompleter.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Projects + class TemplateAutocompleter < ApplicationForm + extend Dry::Initializer + + option :template_id, optional: true + option :parent_id, optional: true + + form do |f| + f.project_autocompleter( + name: :template_id, + label: I18n.t("js.project.use_template"), + autocomplete_options: { + focusDirectly: false, + dropdownPosition: "bottom", + inputValue: template_id, + placeholder: I18n.t("js.project.no_template_selected"), + filters: [ + { name: "user_action", operator: "=", values: ["projects/copy"] }, + { name: "templated", operator: "=", values: ["t"] } + ], + data: { + action: "change->highlight-when-value-selected#itemSelected change->auto-submit#submit", + "qa-field-name": "use_template" + } + } + ) + + f.hidden name: :parent_id, value: parent_id + end + end +end diff --git a/app/forms/projects/template_form.rb b/app/forms/projects/template_form.rb new file mode 100644 index 00000000000..5fb78961b04 --- /dev/null +++ b/app/forms/projects/template_form.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Projects + class TemplateForm < ApplicationForm + extend Dry::Initializer + + option :template + option :copy_options + + form do |f| + f.hidden name: :template_id, value: template.id, scope_name_to_model: false + + f.fields_for(:copy_options, copy_options, nested: false) do |builder| + CopyOptionsForm.new(builder) + end + end + end +end diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb index 9837b2701d4..d8f9fc8e599 100644 --- a/app/views/projects/new.html.erb +++ b/app/views/projects/new.html.erb @@ -33,4 +33,18 @@ See COPYRIGHT and LICENSE files for more details. header.with_breadcrumbs([{ href: home_path, text: organization_name }, t(:label_project_new)]) end %> -<%= angular_component_tag "opce-new-project" %> + +<%= + render Projects::TemplateSelectComponent.new( + template: @template, + parent: @parent + ) +%> + +<%= + render Projects::NewComponent.new( + project: @new_project, + template: @template, + copy_options: @copy_options + ) +%> diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 303aa2984f9..a9fd05910b3 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -30,7 +30,7 @@ Rails.application.reloader.to_prepare do OpenProject::AccessControl.map do |map| map.project_module nil, order: 100 do map.permission :add_project, - { projects: %i[new] }, + { projects: %i[new create] }, permissible_on: :global, require: :loggedin, contract_actions: { projects: %i[create] } @@ -202,7 +202,7 @@ Rails.application.reloader.to_prepare do require: :member map.permission :add_subprojects, - { projects: %i[new] }, + { projects: %i[new create] }, permissible_on: :project, require: :member diff --git a/config/locales/en.yml b/config/locales/en.yml index 38ca8e7808c..fb4d1caab3b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -936,6 +936,11 @@ en: actionview_instancetag_blank_option: "Please select" + activemodel: + attributes: + projects/copy_options: + dependencies: "Dependencies" + activerecord: attributes: announcements: @@ -3464,6 +3469,8 @@ en: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_unsuccessful_create: "Creation failed." + notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." notice_unsuccessful_update_with_reason: "Update failed: %{reason}" notice_successful_update_custom_fields_added_to_project: | diff --git a/config/routes.rb b/config/routes.rb index 8c44afb5e3d..d51bd3b5ee7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -255,7 +255,7 @@ Rails.application.routes.draw do resource :menu, only: %i[show] end - resources :projects, except: %i[show edit create update] do + resources :projects, except: %i[show edit update] do scope module: "projects" do namespace "settings" do resource :general, only: %i[show update], controller: "general" do diff --git a/frontend/src/stimulus/controllers/highlight-when-value-selected.controller.ts b/frontend/src/stimulus/controllers/highlight-when-value-selected.controller.ts new file mode 100644 index 00000000000..b14b0abb5bd --- /dev/null +++ b/frontend/src/stimulus/controllers/highlight-when-value-selected.controller.ts @@ -0,0 +1,44 @@ +/* + * -- copyright + * OpenProject is an open source project management software. + * Copyright (C) the OpenProject GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 3. + * + * OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: + * Copyright (C) 2006-2013 Jean-Philippe Lang + * Copyright (C) 2010-2013 the ChiliProject Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * See COPYRIGHT and LICENSE files for more details. + * ++ + */ + +import { Controller } from '@hotwired/stimulus'; +import { IAutocompleteItem } from 'core-app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component'; + +export default class HighlightWhenValueSelectedController extends Controller { + static targets = ['content']; + static classes = ['highlight']; + + declare readonly contentTarget:HTMLElement; + declare readonly highlightClass:string; + + itemSelected({ detail: item }:CustomEvent):void { + this.contentTarget.classList.toggle(this.highlightClass, item != null); + } +} diff --git a/frontend/src/stimulus/setup.ts b/frontend/src/stimulus/setup.ts index 83572144fa3..608277a307b 100644 --- a/frontend/src/stimulus/setup.ts +++ b/frontend/src/stimulus/setup.ts @@ -21,6 +21,7 @@ import HoverCardTriggerController from './controllers/hover-card-trigger.control import ScrollIntoViewController from './controllers/scroll-into-view.controller'; import CkeditorFocusController from './controllers/ckeditor-focus.controller'; import AddMeetingParamsController from './controllers/add-meeting-params.controller'; +import HighlightWhenValueSelectedController from './controllers/highlight-when-value-selected.controller'; import AutoSubmit from '@stimulus-components/auto-submit'; @@ -59,5 +60,6 @@ instance.register('pattern-input', PatternInputController); instance.register('scroll-into-view', ScrollIntoViewController); instance.register('ckeditor-focus', CkeditorFocusController); instance.register('add-meeting-params', AddMeetingParamsController); +instance.register('highlight-when-value-selected', HighlightWhenValueSelectedController); instance.register('auto-submit', AutoSubmit); diff --git a/spec/components/projects/new_component_spec.rb b/spec/components/projects/new_component_spec.rb new file mode 100644 index 00000000000..f0b57eef57b --- /dev/null +++ b/spec/components/projects/new_component_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "rails_helper" + +RSpec.describe Projects::NewComponent, type: :component do + let(:project) { build_stubbed(:project) } + + def render_component(**params) + render_inline(described_class.new(project:, **params)) + page + end + + it "renders a form" do + expect(render_component).to have_css "form" + end +end diff --git a/spec/components/projects/template_select_component_spec.rb b/spec/components/projects/template_select_component_spec.rb new file mode 100644 index 00000000000..9a27ef76b39 --- /dev/null +++ b/spec/components/projects/template_select_component_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "rails_helper" + +RSpec.describe Projects::TemplateSelectComponent, type: :component do + let(:template) { build_stubbed(:template_project) } + + def render_component(**params) + render_inline(described_class.new(template:, **params)) + page + end + + it "renders a form" do + expect(render_component).to have_css "form" + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 1eb1227c649..705c40da3e1 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -32,35 +32,200 @@ require "spec_helper" RSpec.describe ProjectsController do shared_let(:admin) { create(:admin) } - let(:non_member) { create(:non_member) } + + let(:user) { admin } before do allow(controller).to receive(:set_localization) - login_as admin + login_as user end describe "#new" do - it "renders 'new'" do - get "new" - expect(response).to be_successful - expect(response).to render_template "new" - end + shared_examples_for "successful requests" do + context "without a parent" do + let(:parent) { nil } - context "by non-admin user with add_project permission" do - let(:non_member_user) { create(:user) } + context "without a template" do + let(:template) { nil } - before do - non_member.add_permission! :add_project - login_as non_member_user + it_behaves_like "successful request" + end + + context "with a template" do + let(:template) { create(:template_project) } + + it_behaves_like "successful request" + end end - it "accepts get" do - get :new + context "with a parent" do + let(:parent) { create(:project) } + + context "without a template" do + let(:template) { nil } + + it_behaves_like "successful request" + end + + context "with a template" do + let(:template) { create(:template_project) } + + it_behaves_like "successful request" + end + end + end + + shared_examples_for "successful request" do + it "renders 'new'", :aggregate_faulures do expect(response).to be_successful + expect(assigns(:new_project)).to be_a_new(Project) + expect(assigns(:parent)).to eq parent + expect(assigns(:template)).to eq template expect(response).to render_template "new" end end + + before do + get :new, params: { parent_id: parent&.id, template_id: template&.id } + end + + context "as an admin" do + it_behaves_like "successful requests" + end + + context "as a non-admin with global add_project permission" do + let(:user) { create(:user, global_permissions: [:add_project]) } + let(:template) { nil } + + context "without a parent" do + let(:parent) { nil } + + it_behaves_like "successful request" + end + + context "with a parent with public permissions" do + let(:user) { create(:user, global_permissions: [:add_project], member_with_roles: { parent => parent_role }) } + let(:parent_role) { create(:project_role) } + let(:parent) { create(:project) } + + it_behaves_like "successful request" + end + end + + context "as a non-admin without global add_project permission" do + let(:user) { create(:user, global_permissions: []) } + let(:template) { nil } + + context "without a parent" do + let(:parent) { nil } + + it "returns 403 Not Authorized" do + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "with a parent with add_subprojects permissions" do + let(:user) { create(:user, global_permissions: [], member_with_roles: { parent => parent_role }) } + let(:parent_role) { create(:project_role, permissions: [:add_subprojects]) } + let(:parent) { create(:project) } + let(:template) { nil } + + it_behaves_like "successful request" + end + end + end + + describe "#create" do + context "without a template" do + before do + creation_service = instance_double(Projects::CreateService, call: service_result) + + allow(Projects::CreateService) + .to receive(:new) + .with(user: admin) + .and_return(creation_service) + end + + context "when service call succeeds" do + let(:project) { build_stubbed(:project) } + let(:service_result) { ServiceResult.success(result: project) } + + it "redirects to project show", :aggregate_failures do + post :create, params: { project: { name: "New Project" } } + + expect(response).to redirect_to project_path(project) + expect(flash[:notice]).to eq I18n.t(:notice_successful_create) + end + end + + context "when service call fails" do + let(:project) { Project.new } + let(:service_result) { ServiceResult.failure(result: project, message: "") } + + it "renders new template with errors", :aggregate_failures do + post :create, params: { project: { name: "" } } + + expect(response).not_to be_successful + expect(response).to have_http_status :unprocessable_entity + expect(assigns(:new_project)).to be_a_new(Project) + expect(assigns(:new_project)).not_to be_valid + expect(flash[:error]).to start_with I18n.t(:notice_unsuccessful_create_with_reason, reason: "") + expect(response).to render_template "new" + end + end + end + + context "with a template" do + let(:template) { create(:template_project) } + + before do + copy_service = instance_double(Projects::EnqueueCopyService, call: service_result) + + allow(Projects::EnqueueCopyService) + .to receive(:new) + .with(user: admin, model: template) + .and_return(copy_service) + end + + context "when service call succeeds" do + let(:job) { CopyProjectJob.new } + let(:service_result) { ServiceResult.success(result: job) } + + it "redirects to job status", :aggregate_failures do + post :create, params: { + template_id: template.id, + project: { name: "New Project" }, + copy_options: { dependencies: [], send_notifications: false } + } + + expect(response).to redirect_to job_status_path(job.job_id) + end + end + + context "when service call fails" do + let(:project) { Project.new } + let(:service_result) { ServiceResult.failure(result: project, message: "") } + + it "renders new template with errors", :aggregate_failures do + post :create, params: { + template_id: template.id, + project: { name: "" }, + copy_options: { dependencies: [], send_notifications: false } + } + + expect(response).not_to be_successful + expect(response).to have_http_status :unprocessable_entity + expect(assigns(:new_project)).to be_a_new(Project) + expect(assigns(:new_project)).not_to be_valid + expect(assigns(:template)).not_to be_nil + expect(assigns(:copy_options)).not_to be_nil + expect(flash[:error]).to start_with I18n.t(:notice_unsuccessful_create_with_reason, reason: "") + expect(response).to render_template "new" + end + end + end end describe "index.html" do diff --git a/spec/features/global_roles/global_create_project_spec.rb b/spec/features/global_roles/global_create_project_spec.rb index f9cd8e410a7..2e4beb09de8 100644 --- a/spec/features/global_roles/global_create_project_spec.rb +++ b/spec/features/global_roles/global_create_project_spec.rb @@ -81,17 +81,15 @@ RSpec.describe "Global role: Global Create project", roles: [global_role]) end - let(:name_field) { FormFields::InputFormField.new :name } - current_user { user } it 'allows creating projects via the "+ Project" button' do projects_page.visit! projects_page.navigate_to_new_project_page_from_toolbar_items - name_field.set_value "New project name" + fill_in "Name", with: "New project name" - find("button:not([disabled])", text: "Save").click + click_on "Create" expect(page).to have_current_path "/projects/new-project-name/" end diff --git a/spec/features/projects/attribute_help_texts_spec.rb b/spec/features/projects/attribute_help_texts_spec.rb index 0fc73c9a8d3..d180322d940 100644 --- a/spec/features/projects/attribute_help_texts_spec.rb +++ b/spec/features/projects/attribute_help_texts_spec.rb @@ -35,11 +35,14 @@ RSpec.describe "Project attribute help texts", :js do let(:instance) do create(:project_help_text, - attribute_name: :status, - help_text: "Some **help text** for status.") + attribute_name: :name, + help_text: "Some **help text** for name.") create(:project_help_text, attribute_name: :description, help_text: "Some **help text** for description.") + create(:project_help_text, + attribute_name: :status, + help_text: "Some **help text** for status.") end let(:grid) do @@ -87,19 +90,27 @@ RSpec.describe "Project attribute help texts", :js do it_behaves_like "allows to view help texts" - it "shows the help text on the project create form" do + it "shows the help text on the project create form", :selenium do visit new_project_path - page.find(".op-fieldset--legend", text: "ADVANCED SETTINGS").click + expect(page).to have_field "Name" - expect(page).to have_css(".spot-form-field--label attribute-help-text", wait: 10) + within(:element, "label", text: "Name") do + click_on accessible_name: "Show help text" + end - # Open help text modal - modal.open! - expect(modal.modal_container).to have_css("strong", text: "help text") - modal.expect_edit(editable: user.allowed_globally?(:edit_attribute_help_texts)) + expect(page).to have_modal "Name" - modal.close! + within_modal "Name" do + expect(page).to have_css "strong", text: "help text" + + expect(page).to have_button "Close" + expect(page).to have_link "Edit" + + click_on "Close" + end + + expect(page).not_to have_modal "Name" end end diff --git a/spec/features/projects/create_spec.rb b/spec/features/projects/create_spec.rb index 6770f358212..b9e2684ab23 100644 --- a/spec/features/projects/create_spec.rb +++ b/spec/features/projects/create_spec.rb @@ -32,7 +32,6 @@ require "spec_helper" RSpec.describe "Projects", "creation", :js do - shared_let(:name_field) { FormFields::InputFormField.new :name } shared_let(:project_custom_field_section) { create(:project_custom_field_section, name: "Section A") } current_user { create(:admin) } @@ -49,6 +48,8 @@ RSpec.describe "Projects", "creation", it "can navigate to the create project page" do projects_page.navigate_to_new_project_page_from_toolbar_items + expect(page).to have_heading "New project" + expect(page).to have_current_path(new_project_path) end end @@ -56,8 +57,10 @@ RSpec.describe "Projects", "creation", it "can create a project" do projects_page.navigate_to_new_project_page_from_toolbar_items - name_field.set_value "Foo bar" - click_button "Save" + expect(page).to have_heading "New project" + + fill_in "Name", with: "Foo bar" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ expect(page).to have_content "Foo bar" @@ -66,8 +69,10 @@ RSpec.describe "Projects", "creation", it "does not create a project with an already existing identifier" do projects_page.navigate_to_new_project_page_from_toolbar_items - name_field.set_value "Foo project" - click_on "Save" + expect(page).to have_heading "New project" + + fill_in "Name", with: "Foo project" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-project-1\/?/ @@ -77,20 +82,30 @@ RSpec.describe "Projects", "creation", context "with a multi-select list custom field" do shared_let(:list_custom_field) do - create(:list_project_custom_field, name: "List CF", multi_value: true, project_custom_field_section:) + create(:list_project_custom_field, + name: "List CF", + is_required: true, + multi_value: true, + project_custom_field_section:) + end + let(:list_field) do + FormFields::SelectFormField.new( + list_custom_field, + selector: "[data-qa-field-name='#{list_custom_field.attribute_name(:kebab_case)}'" + ) end - let(:list_field) { FormFields::SelectFormField.new list_custom_field } it "can create a project" do projects_page.navigate_to_new_project_page_from_toolbar_items - name_field.set_value "Foo bar" + expect(page).to have_heading "New project" - find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click + fill_in "Name", with: "Foo bar" + expect(page).to have_combo_box "List CF *" list_field.select_option "A", "B" - click_button "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ expect(page).to have_content "Foo bar" @@ -120,18 +135,26 @@ RSpec.describe "Projects", "creation", shared_let(:version_custom_field) do create(:version_project_custom_field, name: "Version CF", + is_required: true, multi_value: true, project_custom_field_section:) end - let(:version_field) { FormFields::SelectFormField.new version_custom_field } + let(:version_field) do + FormFields::SelectFormField.new( + version_custom_field, + selector: "[data-qa-field-name='#{version_custom_field.attribute_name(:kebab_case)}'" + ) + end it "can create a project" do projects_page.navigate_to_new_project_page_from_toolbar_items - name_field.set_value "Foo bar" + expect(page).to have_heading "New project" - find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click + fill_in "Name", with: "Foo bar" + + expect(page).to have_combo_box "Version CF *" # expect the versions are grouped by the project name version_field.expect_option(versions.first.name, grouping: project.name) @@ -139,7 +162,7 @@ RSpec.describe "Projects", "creation", version_field.select_option(versions.first.name, versions.last.name) - click_button "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ expect(page).to have_content "Foo bar" @@ -155,7 +178,7 @@ RSpec.describe "Projects", "creation", it "hides the active field and the identifier" do visit new_project_path - find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click + expect(page).to have_heading "New project" expect(page).to have_no_content "Active" expect(page).to have_no_content "Identifier" @@ -184,19 +207,14 @@ RSpec.describe "Projects", "creation", project_custom_field_section:) end - it "separates optional and required custom fields for new" do + it "renders required custom fields for new" do visit new_project_path - expect(page).to have_content "Required Foo" - expect(page).to have_content "Required User" + expect(page).to have_heading "New project" - click_on "Advanced settings" - - within(".op-fieldset") do - expect(page).to have_text "Optional Foo" - expect(page).to have_no_text "Required Foo" - expect(page).to have_no_text "Required User" - end + expect(page).to have_field "Required Foo", required: true + expect(page).to have_field "Required User *" # FIXME required: true + expect(page).to have_no_field "Optional Foo" end end @@ -206,10 +224,11 @@ RSpec.describe "Projects", "creation", end it "requires the required custom field" do - click_on "Save" + expect(page).to have_heading "New project" - expect(page).to have_content "Required Foo can't be blank" - expect(page).to have_no_content "Optional Foo can't be blank" + click_on "Create" + + expect(page).to have_field "Required Foo", validation_error: "can't be blank." end end @@ -222,17 +241,15 @@ RSpec.describe "Projects", "creation", before do visit new_project_path + + expect(page).to have_heading "New project" # rubocop:disable RSpec/ExpectInHook + fill_in "Name", with: "Foo bar" fill_in "Required Foo", with: "Required value" - - click_on "Advanced settings" end it "enables custom fields with provided values for this project" do - fill_in "Optional Foo", with: "Optional value" - fill_in "Unused Foo", with: "" - - click_on "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ @@ -240,7 +257,7 @@ RSpec.describe "Projects", "creation", # unused custom field should not be activated expect(project.project_custom_field_ids).to contain_exactly( - optional_custom_field.id, required_custom_field.id + required_custom_field.id ) end @@ -248,13 +265,14 @@ RSpec.describe "Projects", "creation", shared_let(:custom_field_with_default_value) do create(:project_custom_field, name: "Foo with default value", field_format: "string", + is_required: true, default_value: "Default value", project_custom_field_section:) end it "enables custom fields with default values if not set to blank explicitly" do # don't touch the default value - click_on "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ @@ -268,27 +286,10 @@ RSpec.describe "Projects", "creation", expect(project.custom_value_for(custom_field_with_default_value).value).to eq("Default value") end - it "does not enable custom fields with default values if set to blank explicitly" do - # native blank input does not work with this input, using support class here - field = FormFields::InputFormField.new(custom_field_with_default_value) - field.set_value("") - - click_on "Save" - - expect(page).to have_current_path /\/projects\/foo-bar\/?/ - - project = Project.last - - # custom_field_with_default_value should not be activated - expect(project.project_custom_field_ids).to contain_exactly( - required_custom_field.id - ) - end - it "does enable custom fields with default values if overwritten with a new value" do fill_in "Foo with default value", with: "foo" - click_on "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ @@ -303,86 +304,10 @@ RSpec.describe "Projects", "creation", end end - context "with correct handling of optional boolean values" do - shared_let(:custom_boolean_field_default_true) do - create(:project_custom_field, name: "Boolean with default true", - field_format: "bool", - default_value: true, - project_custom_field_section:) - end - - shared_let(:custom_boolean_field_default_false) do - create(:project_custom_field, name: "Boolean with default false", - field_format: "bool", - default_value: false, - project_custom_field_section:) - end - - shared_let(:custom_boolean_field_with_no_default) do - create(:project_custom_field, name: "Boolean with no default", - field_format: "bool", - project_custom_field_section:) - end - - it "only enables boolean custom fields with default values if untouched" do - # do not touch any of the boolean fields - click_on "Save" - - expect(page).to have_current_path /\/projects\/foo-bar\/?/ - - project = Project.last - - expect(project.project_custom_field_ids).to contain_exactly( - required_custom_field.id, - custom_boolean_field_default_true.id, - custom_boolean_field_default_false.id - ) - - expect(project.custom_value_for(custom_boolean_field_default_true).typed_value).to be_truthy - expect(project.custom_value_for(custom_boolean_field_default_false).typed_value).to be_falsy - end - - it "enables boolean custom fields without default values if set to true explicitly" do - check "Boolean with no default" - - click_on "Save" - - expect(page).to have_current_path /\/projects\/foo-bar\/?/ - - project = Project.last - - expect(project.project_custom_field_ids).to contain_exactly( - required_custom_field.id, - custom_boolean_field_default_true.id, - custom_boolean_field_default_false.id, - custom_boolean_field_with_no_default.id - ) - - expect(project.custom_value_for(custom_boolean_field_with_no_default).typed_value).to be_truthy - end - - it "enables boolean custom fields with default values if set to false explicitly" do - uncheck "Boolean with default true" - - click_on "Save" - - expect(page).to have_current_path /\/projects\/foo-bar\/?/ - - project = Project.last - - expect(project.project_custom_field_ids).to contain_exactly( - required_custom_field.id, - custom_boolean_field_default_true.id, - custom_boolean_field_default_false.id - ) - - expect(project.custom_value_for(custom_boolean_field_default_true).typed_value).to be_falsy - end - end - context "with correct handling of invisible values" do shared_let(:invisible_field) do create(:string_project_custom_field, name: "Text for Admins only", + is_required: true, admin_only: true, project_custom_field_section:) end @@ -393,7 +318,7 @@ RSpec.describe "Projects", "creation", fill_in "Text for Admins only", with: "foo" - click_on "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ @@ -411,9 +336,11 @@ RSpec.describe "Projects", "creation", current_user { create(:user, global_permissions: %i[add_project]) } it "does not show invisible fields in the form and thus not activates the invisible field" do + pending "Admin-only project attributes currently prevent users from creating projects (OP#64479)" + expect(page).to have_no_content "Text for Admins only" - click_on "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-bar\/?/ diff --git a/spec/features/projects/project_status_administration_spec.rb b/spec/features/projects/project_status_administration_spec.rb deleted file mode 100644 index 2ec2e674ab9..00000000000 --- a/spec/features/projects/project_status_administration_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -require "spec_helper" - -RSpec.describe "Projects status administration", :js do - include_context "ng-select-autocomplete helpers" - - let(:current_user) do - create(:user) do |u| - create(:global_member, - principal: u, - roles: [create(:global_role, permissions: global_permissions)]) - end - end - let(:global_permissions) { [:add_project] } - let(:project_permissions) { [:edit_project] } - let!(:project_role) do - create(:project_role, permissions: project_permissions) do |r| - allow(Setting) - .to receive(:new_project_user_role_id) - .and_return(r.id.to_s) - end - end - let(:status_description) { Components::WysiwygEditor.new('[data-qa-field-name="statusExplanation"]') } - - let(:name_field) { FormFields::InputFormField.new :name } - let(:status_field) { FormFields::SelectFormField.new :status } - - before do - login_as current_user - end - - it "allows setting the status on project creation" do - visit new_project_path - - # Create the project with status - click_button "Advanced settings" - - name_field.set_value "New project" - status_field.select_option "On track" - - status_description.set_markdown "Everything is fine at the start" - status_description.expect_supports_macros - - click_button "Save" - - expect(page).to have_current_path /projects\/new-project\/?/ - - # Check that the status has been set correctly - visit project_settings_general_path(project_id: "new-project") - - status_description.expect_value "Everything is fine at the start" - - status_description.set_markdown "Oh no" - - click_button "Update status description" - - status_description.expect_value "Oh no" - end -end diff --git a/spec/features/projects/subproject_creation_spec.rb b/spec/features/projects/subproject_creation_spec.rb index 5077614d396..6d08b48560c 100644 --- a/spec/features/projects/subproject_creation_spec.rb +++ b/spec/features/projects/subproject_creation_spec.rb @@ -31,7 +31,6 @@ require "spec_helper" RSpec.describe "Subproject creation", :js do - let(:name_field) { FormFields::InputFormField.new :name } let(:parent_field) { FormFields::SelectFormField.new :parent } let(:add_subproject_role) { create(:project_role, permissions: %i[edit_project add_subprojects]) } let(:view_project_role) { create(:project_role, permissions: %i[edit_project]) } @@ -55,16 +54,20 @@ RSpec.describe "Subproject creation", :js do end it "can create a subproject" do - click_link "Subproject" + click_on "New subproject" + + expect(page).to have_heading "New project" + + fill_in "Name", with: "Foo child" + + expect(page).to have_combo_box "Subproject of" - name_field.set_value "Foo child" - parent_field.expect_required # The other project is not a valid parent since the user is lacking # the add_subproject permission therein. parent_field.expect_no_option(other_project.name) parent_field.expect_selected parent_project.name - click_button "Save" + click_on "Create" expect(page).to have_current_path /\/projects\/foo-child\/?/ diff --git a/spec/features/projects/template_spec.rb b/spec/features/projects/template_spec.rb index ffa7e128419..dbd048da50b 100644 --- a/spec/features/projects/template_spec.rb +++ b/spec/features/projects/template_spec.rb @@ -85,10 +85,7 @@ RSpec.describe "Project templates", :js, with_good_job_batches: [CopyProjectJob, create(:user, member_with_roles: { template => role }) end - let(:name_field) { FormFields::InputFormField.new :name } let(:template_field) { FormFields::SelectFormField.new :use_template } - let(:status_field) { FormFields::SelectFormField.new :status } - let(:parent_field) { FormFields::SelectFormField.new :parent } current_user do create(:user, @@ -99,42 +96,37 @@ RSpec.describe "Project templates", :js, with_good_job_batches: [CopyProjectJob, it "can instantiate the project with the copy permission" do visit new_project_path - name_field.set_value "Foo bar" + fill_in "Name", with: "Foo bar" - expect(page) - .to have_no_content("COPY OPTIONS") + expect(page).to have_no_selector :fieldset, "Copy options" template_field.select_option "My template" # Only when a template is selected, the options are displayed. # Using this to know when the copy form has been fetched from the backend. - expect(page) - .to have_content("COPY OPTIONS") + expect(page).to have_selector :fieldset, "Copy options" + + # FIXME: It should keep the name. See BUG OP#64594 https://community.openproject.org/wp/64594 + # expect(page).to have_field "Name", with: "Foo bar" + fill_in "Name", with: "Foo bar" - # It keeps the name - name_field.expect_value "Foo bar" template_field.expect_selected "My template" - # Updates the identifier in advanced settings - page.find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click - status_field.expect_selected "ON TRACK" + expect(page).to have_unchecked_field "Send email notifications during the project copy" - # Update status to off track - status_field.select_option "Off track" - parent_field.select_option other_project.name + within_fieldset "Copy options" do + # And allows to deselect copying the members. + uncheck "Project members" + end - page.find(".op-fieldset--toggle", text: "COPY OPTIONS").click + click_on "Create" - # Now shows the send notifications field. - expect(page).to have_css('[data-qa-field-name="sendNotifications"]') + expect(page).to have_dialog "Background job status" - # And allows to deselect copying the members. - uncheck I18n.t(:"projects.copy.members") - - page.find("button:not([disabled])", text: "Save").click - - expect(page).to have_content I18n.t(:label_copy_project) - expect(page).to have_content I18n.t("job_status_dialog.generic_messages.in_queue") + within_dialog "Background job status" do + expect(page).to have_heading "Copy project" + expect(page).to have_text "The job has been queued and will be processed shortly." + end # Run background jobs twice: the background job which itself enqueues the mailer job GoodJob.perform_inline diff --git a/spec/forms/projects/copy_options_form_spec.rb b/spec/forms/projects/copy_options_form_spec.rb new file mode 100644 index 00000000000..71e14f7ddea --- /dev/null +++ b/spec/forms/projects/copy_options_form_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" + +RSpec.describe Projects::CopyOptionsForm, type: :forms do + include_context "with rendered form" + + let(:model) { Projects::CopyOptions.new } + + shared_examples "rendering dependency checkbox" do |locator| + it "renders checked checkbox for '#{locator}'" do + expect(page).to have_checked_field locator, fieldset: "Copy options" + end + end + + describe "dependencies" do + include_examples "rendering dependency checkbox", "Project members" + include_examples "rendering dependency checkbox", "Versions" + include_examples "rendering dependency checkbox", "Work packages: categories" + include_examples "rendering dependency checkbox", "Work packages" + include_examples "rendering dependency checkbox", "Work packages: attachments" + include_examples "rendering dependency checkbox", "Work packages: shares" + include_examples "rendering dependency checkbox", "Wiki pages" + include_examples "rendering dependency checkbox", "Wiki pages: attachments" + include_examples "rendering dependency checkbox", "Forums" + include_examples "rendering dependency checkbox", "Work packages: saved views" + include_examples "rendering dependency checkbox", "Boards" + include_examples "rendering dependency checkbox", "Project overview" + include_examples "rendering dependency checkbox", "File storages" + include_examples "rendering dependency checkbox", "File storages: Project folders" + include_examples "rendering dependency checkbox", "Work packages: file links" + end + + describe "notifications" do + it "renders unchecked checkbox for email notifications" do + expect(page).to have_unchecked_field "Send email notifications during the project copy" + end + end +end diff --git a/spec/forms/projects/copy_options_spec.rb b/spec/forms/projects/copy_options_spec.rb new file mode 100644 index 00000000000..f23a0d5fbcc --- /dev/null +++ b/spec/forms/projects/copy_options_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" + +RSpec.describe Projects::CopyOptions, type: :model do + describe "#dependencies" do + let(:all_dependencies) { Projects::CopyService.copyable_dependencies.pluck(:identifier) } + + it "has a default value" do + expect(subject.dependencies).to match_array(all_dependencies) + end + + it "validates dependencies" do + expect(subject).to validate_inclusion_of(:dependencies).in_array(all_dependencies) + end + end + + describe "#send_notifications" do + it "has a default value" do + expect(subject.send_notifications).to be false + end + end +end diff --git a/spec/forms/projects/settings/custom_fields_form_spec.rb b/spec/forms/projects/settings/custom_fields_form_spec.rb new file mode 100644 index 00000000000..781fcae7937 --- /dev/null +++ b/spec/forms/projects/settings/custom_fields_form_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" + +RSpec.describe Projects::Settings::CustomFieldsForm, type: :forms do + let(:string_project_custom_field) { create(:string_project_custom_field, name: "String field", is_required: true) } + let(:boolean_project_custom_field) { create(:boolean_project_custom_field, name: "Boolean field", is_required: true) } + let(:text_project_custom_field) { create(:text_project_custom_field, name: "Text field", is_required: true) } + let(:integer_project_custom_field) { create(:integer_project_custom_field, name: "Integer field", is_required: true) } + let(:float_project_custom_field) { create(:float_project_custom_field, name: "Float field", is_required: true) } + let(:date_project_custom_field) { create(:date_project_custom_field, name: "Date field", is_required: true) } + let(:list_project_custom_field) do + create(:list_project_custom_field, name: "List field", is_required: true, possible_values: ["eins", "zwei", "drei"]) + end + let(:multi_list_project_custom_field) do + create(:list_project_custom_field, + name: "Multi-list field", + is_required: true, + multi_value: true, + possible_values: ["uno", "due", "tre", "quattro"]) + end + let(:version_project_custom_field) { create(:version_project_custom_field, name: "Version field", is_required: true) } + let(:user_project_custom_field) { create(:user_project_custom_field, name: "User field", is_required: true) } + let(:link_project_custom_field) { create(:link_project_custom_field, name: "Link field", is_required: true) } + + let(:user) { create(:user) } + let(:version) { create(:version) } + + let(:custom_field_values) do + { + "#{string_project_custom_field.id}": "str_val", + "#{boolean_project_custom_field.id}": true, + "#{integer_project_custom_field.id}": 43, + "#{float_project_custom_field.id}": 78.23, + "#{date_project_custom_field.id}}": Date.civil(2024, 0o3, 20), + "#{link_project_custom_field.id}}": "https://rubygems.org/", + "#{list_project_custom_field.id}}": list_project_custom_field.possible_values.first.id, + "#{multi_list_project_custom_field.id}}": multi_list_project_custom_field.possible_values.last(2).map(&:id), + "#{version_project_custom_field.id}}": version, + "#{user_project_custom_field.id}}": user + } + end + + let(:model) { create(:project, custom_field_values:) } + let(:current_user) { build_stubbed(:admin) } + + current_user { build_stubbed(:admin) } + + include_context "with rendered form" + + it "renders HTML input fields", :aggregate_failures do + expect(page).to have_field "String field", with: "str_val", required: true + expect(page).to have_checked_field "Boolean field", required: true + expect(page).to have_field "Integer field", type: :number, with: "43", required: true + expect(page).to have_field "Float field", type: :number, with: "78.23", required: true + expect(page).to have_field "Date field", type: :date, with: "2024-03-20", required: true + expect(page).to have_field "Link field", with: "https://rubygems.org/", required: true + end + + it "renders list field" do + expect(page).to have_element :label, text: "List field" + + label_id = page.find(:element, :label, text: "List field")["for"] + expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter| + expect(autocompleter["data-multiple"]).to be_json_eql(%{false}) + expect(autocompleter["data-items"]).to have_json_size(3) + expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "eins"}}) + end + end + + it "renders multi-list field" do + expect(page).to have_element :label, text: "Multi-list field" + + label_id = page.find(:element, :label, text: "Multi-list field")["for"] + expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter| + expect(autocompleter["data-multiple"]).to be_json_eql(%{true}) + expect(autocompleter["data-items"]).to have_json_size(4) + expect(autocompleter["data-model"]).to have_json_size(2) + expect(autocompleter["data-model"]).to be_json_eql(%{[{"name": "tre"}, {"name": "quattro"}]}) + end + end + + it "renders version field" do + expect(page).to have_element :label, text: "Version field" + + label_id = page.find(:element, :label, text: "Version field")["for"] + expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter| + expect(autocompleter["data-items"]).to have_json_size(0) + expect(autocompleter["data-model"]).to be_json_eql(%{null}) + end + end + + it "renders user field" do + expect(page).to have_element :label, text: "User field" + + label_id = page.find(:element, :label, text: "User field")["for"] + expect(page).to have_element "opce-user-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter| + expect(autocompleter["data-resource"]).to be_json_eql(%{"principals"}) + expect(autocompleter["data-url"]).to be_json_eql(%{"/api/v3/principals"}) + expect(autocompleter["data-input-value"]).to be_json_eql(%{"#{user.id}"}) + end + end +end diff --git a/spec/forms/projects/submit_or_cancel_spec.rb b/spec/forms/projects/submit_or_cancel_spec.rb new file mode 100644 index 00000000000..faaed92a64f --- /dev/null +++ b/spec/forms/projects/submit_or_cancel_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" + +RSpec.describe Projects::SubmitOrCancel, type: :forms do + include_context "with rendered form" + + let(:model) { build_stubbed(:project) } + + it "renders a horizontal group" do + expect(page).to have_css ".FormControl-horizontalGroup" + end + + it "renders submit button" do + expect(page).to have_button "Create", class: "Button--primary" + end + + it "renders cancel link" do + expect(page).to have_link "Cancel", class: "Button--secondary" + end +end diff --git a/spec/forms/projects/template_autocompleter_spec.rb b/spec/forms/projects/template_autocompleter_spec.rb new file mode 100644 index 00000000000..8f6fa30aac5 --- /dev/null +++ b/spec/forms/projects/template_autocompleter_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" + +RSpec.describe Projects::TemplateAutocompleter, type: :forms do + include ViewComponent::TestHelpers + + def render_form + render_in_view_context(template_id, described_class) do |template_id, described_class| + primer_form_with(url: "/foo") do |f| + render(described_class.new(f, template_id:)) + end + end + end + + before do + render_form + end + + let(:template_id) { 1001 } + + it "renders field" do + expect(page).to have_element "opce-project-autocompleter", "data-input-name": "\"template_id\"" do |element| + expect(element["data-input-value"]).to eq "1001" + end + end + + it "connects Stimulus controller actions" do + expect(page).to have_element "opce-project-autocompleter", "data-input-name": "\"template_id\"" do |element| + expect(element["data-action"]).to include "change->highlight-when-value-selected#itemSelected" + expect(element["data-action"]).to include "change->auto-submit#submit" + end + end +end diff --git a/spec/forms/projects/template_form_spec.rb b/spec/forms/projects/template_form_spec.rb new file mode 100644 index 00000000000..5dabb3c5389 --- /dev/null +++ b/spec/forms/projects/template_form_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" + +RSpec.describe Projects::TemplateForm, type: :forms do + include ViewComponent::TestHelpers + + def render_form + render_in_view_context( + model, + template, + copy_options, + described_class + ) do |model, template, copy_options, described_class| + primer_form_with(url: "/foo", model:) do |f| + render(described_class.new(f, template:, copy_options:)) + end + end + end + + before do + render_form + end + + let(:model) { build_stubbed(:project) } + let(:template) { build_stubbed(:template_project) } + let(:copy_options) { Projects::CopyOptions.new } + + it "renders hidden field" do + expect(page).to have_field "template_id", type: :hidden, with: template.id + end + + it "renders Copy options" do + expect(page).to have_selector :fieldset, "Copy options" + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 41968453fd6..103c3c62734 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -59,6 +59,14 @@ RSpec.describe ProjectsController do end end + describe "create" do + it do + expect(post("/projects")).to route_to( + controller: "projects", action: "create" + ) + end + end + describe "destroy_info" do it do expect(get("/projects/123/destroy_info")).to route_to(