diff --git a/Gemfile b/Gemfile
index db179dcf56d5..1d3c7520dd3d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -232,6 +232,8 @@ gem "view_component", "~> 4.1.1"
# Lookbook
gem "lookbook", "2.3.13"
+gem "inline_svg", "~> 1.10.0"
+
# Require factory_bot for usage with openproject plugins testing
gem "factory_bot", "~> 6.5.6", require: false
# require factory_bot_rails for convenience in core development
diff --git a/Gemfile.lock b/Gemfile.lock
index 7496384cceda..185337d898fb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -718,6 +718,9 @@ GEM
ostruct
ice_cube (0.17.0)
ice_nine (0.11.2)
+ inline_svg (1.10.0)
+ activesupport (>= 3.0)
+ nokogiri (>= 1.6)
interception (0.5)
io-console (0.8.1)
irb (1.15.3)
@@ -1610,6 +1613,7 @@ DEPENDENCIES
i18n-tasks (~> 1.0.13)
ice_cube (~> 0.17.0)
ice_nine
+ inline_svg (~> 1.10.0)
json_schemer (~> 2.4.0)
json_spec (~> 1.1.4)
ladle
@@ -1946,6 +1950,7 @@ CHECKSUMS
icalendar (2.12.1) sha256=ecff56c550aed551f29ad1faad0da54bf62362dfaf22a428bd7ad782938fe764
ice_cube (0.17.0) sha256=32deb45dda4b4acc53505c2f581f6d32b5afc04d29b9004769944a0df5a5fcbe
ice_nine (0.11.2) sha256=5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db
+ inline_svg (1.10.0) sha256=5b652934236fd9f8adc61f3fd6e208b7ca3282698b19f28659971da84bf9a10f
interception (0.5) sha256=a53818d636752a8df90d8c1bb2f7b6e13a7b828543cb02b50fbde98b849d7907
io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb
irb (1.15.3) sha256=4349edff1efa7ff7bfd34cb9df74a133a588ba88c2718098b3b4468b81184aaa
diff --git a/app/components/projects/index_sub_header_component.rb b/app/components/projects/index_sub_header_component.rb
index 992efa4bbcde..20eb6e169c24 100644
--- a/app/components/projects/index_sub_header_component.rb
+++ b/app/components/projects/index_sub_header_component.rb
@@ -72,14 +72,9 @@ def clear_button_id
end
def new_workspace_path(type)
- case type
- when Project.workspace_types[:project]
- new_project_path
- when Project.workspace_types[:portfolio]
- new_portfolio_path
- when Project.workspace_types[:program]
- new_program_path
- end
+ return unless Project.workspace_types.key?(type)
+
+ url_for([:new, type.to_sym])
end
def new_workspace_label(type)
@@ -87,11 +82,13 @@ def new_workspace_label(type)
end
def allowed_new_workspace_types
- allowed_types = []
- allowed_types << Project.workspace_types[:project] if @current_user.allowed_globally?(:add_project)
- allowed_types << Project.workspace_types[:portfolio] if @current_user.allowed_globally?(:add_portfolios) && OpenProject::FeatureDecisions.portfolio_models_active?
- allowed_types << Project.workspace_types[:program] if @current_user.allowed_globally?(:add_programs) && OpenProject::FeatureDecisions.portfolio_models_active?
- allowed_types
+ @allowed_new_workspace_types ||= [].tap do |types|
+ if OpenProject::FeatureDecisions.portfolio_models_active?
+ types << "portfolio" if @current_user.allowed_globally?(:add_portfolios)
+ types << "program" if @current_user.allowed_globally?(:add_programs)
+ end
+ types << "project" if @current_user.allowed_globally?(:add_project)
+ end
end
def for_a_single_new_allowed_type
diff --git a/app/components/projects/template_select_component.html.erb b/app/components/projects/template_select_component.html.erb
index c13df0203903..8c5bac513008 100644
--- a/app/components/projects/template_select_component.html.erb
+++ b/app/components/projects/template_select_component.html.erb
@@ -30,7 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
<%=
component_wrapper do
settings_primer_form_with(
- url: new_project_path,
+ url: new_workspace_path,
method: :get,
data: {
turbo_frame: Projects::NewComponent.wrapper_key,
@@ -38,34 +38,7 @@ See COPYRIGHT and LICENSE files for more details.
auto_submit_delay_value: 0
}
) do |f|
- render(
- OpPrimer::InsetBoxComponent.new(my: 3)
- ) do
- template_id_value = template_id
- parent_id_value = parent_id
- render_inline_form(f) do |form|
- form.project_autocompleter(
- name: :template_id,
- label: I18n.t("create_project.template_label"),
- autocomplete_options: {
- focusDirectly: false,
- dropdownPosition: "bottom",
- inputValue: template_id_value,
- placeholder: I18n.t("label_none_parentheses"),
- filters: [
- { name: "user_action", operator: "=", values: ["projects/copy"] },
- { name: "templated", operator: "=", values: ["t"] }
- ],
- data: {
- action: "change->auto-submit#submit",
- "qa-field-name": "use_template"
- }
- }
- )
-
- form.hidden name: :parent_id, value: parent_id_value
- end
- end
+ render Projects::TemplateSelectForm.new(f, template_id:, parent_id:, workspace_type: project.workspace_type, current_user:)
end
end
%>
diff --git a/app/components/projects/template_select_component.rb b/app/components/projects/template_select_component.rb
index 27d4ed96893b..be25bdb18426 100644
--- a/app/components/projects/template_select_component.rb
+++ b/app/components/projects/template_select_component.rb
@@ -30,14 +30,28 @@
module Projects
class TemplateSelectComponent < ApplicationComponent
+ extend Dry::Initializer[undefined: false]
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
- options :template, :parent
+ option :project
+ option :template
+ option :parent, optional: true
+ option :current_user, default: -> { User.current }
private
+ def new_workspace_path
+ workspace_type = if Project.workspace_types.key?(project.workspace_type)
+ project.workspace_type.to_sym
+ else
+ :project
+ end
+
+ url_for([:new, workspace_type])
+ end
+
def template_id = template&.id
def parent_id = parent&.id
end
diff --git a/app/forms/projects/template_select_form.rb b/app/forms/projects/template_select_form.rb
new file mode 100644
index 000000000000..9bc6ce2c1ce6
--- /dev/null
+++ b/app/forms/projects/template_select_form.rb
@@ -0,0 +1,107 @@
+# 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 TemplateSelectForm < ApplicationForm
+ extend Dry::Initializer[undefined: false]
+ include OpenProject::TextFormatting
+
+ BLANK_VALUE = ""
+ private_constant :BLANK_VALUE
+
+ option :template_id
+ option :parent_id, optional: true
+ option :workspace_type
+ option :current_user, default: -> { User.current }
+
+ delegate :strip_tags, to: :@view_context
+
+ form do |f|
+ f.advanced_radio_button_group(
+ name: :template_id,
+ label: I18n.t("create_project.template_label"),
+ scope_name_to_model: false,
+ data: {
+ action: "change->auto-submit#submit",
+ qa_field_name: "use_template"
+ }
+ ) do |group|
+ group.radio_button(
+ value: BLANK_VALUE,
+ label: blank_template_label,
+ id: "template_id_blank",
+ caption: blank_template_caption,
+ checked: template_id.blank?
+ )
+
+ available_templates.each do |template|
+ group.radio_button(
+ value: template.id,
+ label: template.name,
+ caption: format_caption(template.description),
+ checked: template.id == template_id
+ )
+ end
+ end
+
+ f.hidden(name: :parent_id, value: parent_id, scope_name_to_model: false) if parent_id
+ end
+
+ private
+
+ def available_templates
+ @available_templates ||= Project
+ .visible(current_user)
+ .active
+ .templated
+ .order(name: :asc)
+ end
+
+ def format_caption(text)
+ return I18n.t("create_project.blank_description") if text.blank?
+
+ render(Primer::Beta::Text.new(classes: %w[line-clamp-3 lh-default])) do
+ strip_tags(format_text(text))
+ end
+ end
+
+ def blank_template_label
+ return unless Project.workspace_types.key?(workspace_type)
+
+ I18n.t("create_#{workspace_type}.blank_template.label")
+ end
+
+ def blank_template_caption
+ return unless Project.workspace_types.key?(workspace_type)
+
+ I18n.t("create_#{workspace_type}.blank_template.description")
+ end
+ end
+end
diff --git a/app/helpers/workspace_helper.rb b/app/helpers/workspace_helper.rb
index 86687dd880ac..4ca918f11a2d 100644
--- a/app/helpers/workspace_helper.rb
+++ b/app/helpers/workspace_helper.rb
@@ -29,24 +29,17 @@
#++
module WorkspaceHelper
+ WORKSPACE_ICON_MAPPING = {
+ project: :project,
+ portfolio: :briefcase,
+ program: :"project-roadmap"
+ }.with_indifferent_access.freeze
+
def new_workspace_title(workspace)
- if workspace.project?
- I18n.t(:label_project_new)
- elsif workspace.portfolio?
- I18n.t(:label_portfolio_new)
- elsif workspace.program?
- I18n.t(:label_program_new)
- end
- end
+ return unless Project.workspace_types.key?(workspace.workspace_type)
- def workspace_icon(type)
- case type
- when Project.workspace_types[:portfolio]
- :briefcase
- when Project.workspace_types[:program]
- :"project-roadmap"
- else
- :project
- end
+ I18n.t(:"label_#{workspace.workspace_type}_new")
end
+
+ def workspace_icon(type) = WORKSPACE_ICON_MAPPING[type]
end
diff --git a/app/seeders/demo_data/project_seeder.rb b/app/seeders/demo_data/project_seeder.rb
index 2fe412e0e208..6473058c294a 100644
--- a/app/seeders/demo_data/project_seeder.rb
+++ b/app/seeders/demo_data/project_seeder.rb
@@ -164,7 +164,7 @@ def project_attributes # rubocop:disable Metrics/AbcSize
enabled_module_names: project_data.lookup("modules"),
types: Type.all,
parent: Project.find_by(identifier: project_data.lookup("parent")),
- workspace_type: Project.workspace_types[:project]
+ workspace_type: "project"
}
end
end
diff --git a/app/seeders/development_data/projects_seeder.rb b/app/seeders/development_data/projects_seeder.rb
index 2e71fae45d83..749506389b48 100644
--- a/app/seeders/development_data/projects_seeder.rb
+++ b/app/seeders/development_data/projects_seeder.rb
@@ -113,7 +113,7 @@ def project_data(identifier)
identifier:,
enabled_module_names: project_modules,
types: Type.all,
- workspace_type: Project.workspace_types[:project]
+ workspace_type: "project"
}
end
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
index f0d493df4a4c..eade24d70fe3 100644
--- a/app/views/projects/new.html.erb
+++ b/app/views/projects/new.html.erb
@@ -36,8 +36,10 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render Projects::TemplateSelectComponent.new(
+ project: @new_project,
template: @template,
- parent: @parent
+ parent: @parent,
+ current_user:
)
%>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6831c0626e84..1db8d03a68c8 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2236,6 +2236,20 @@ en:
template_label: "Use template"
copy_options:
dependencies_label: "Copy from template"
+ blank_template:
+ label: "Blank project"
+ description: Start from scratch. Manually add project attributes, members and modules.
+ blank_description: No description provided.
+
+ create_portfolio:
+ blank_template:
+ label: "Blank portfolio"
+ description: Start from scratch. Manually add portfolio attributes, members and modules.
+
+ create_program:
+ blank_template:
+ label: "Blank program"
+ description: Start from scratch. Manually add program attributes, members and modules.
create_wiki_page: "Create new wiki page"
create_wiki_page_button: "Wiki page"
diff --git a/frontend/src/global_styles/content/_index.sass b/frontend/src/global_styles/content/_index.sass
index c8400e513f22..94599def3714 100644
--- a/frontend/src/global_styles/content/_index.sass
+++ b/frontend/src/global_styles/content/_index.sass
@@ -76,6 +76,7 @@
@import activity_list
@import activity_days
@import hierachy_custom_field_layout
+@import text_utils
@import menus/menu_blocks
@import editor/index
diff --git a/frontend/src/global_styles/content/_text_utils.sass b/frontend/src/global_styles/content/_text_utils.sass
new file mode 100644
index 000000000000..0e5b1cddc6e8
--- /dev/null
+++ b/frontend/src/global_styles/content/_text_utils.sass
@@ -0,0 +1,3 @@
+@for $i from 1 through 6
+ .line-clamp-#{$i}
+ @include line-clamp($i)
diff --git a/frontend/src/global_styles/openproject.sass b/frontend/src/global_styles/openproject.sass
index 3716309a004a..d8164407754b 100644
--- a/frontend/src/global_styles/openproject.sass
+++ b/frontend/src/global_styles/openproject.sass
@@ -31,3 +31,4 @@
// Component specific Styles
@import "../../../app/components/_index.sass"
@import "../../../app/forms/_index.sass"
+@import "../../../lib/primer/open_project/forms/_index.sass"
diff --git a/frontend/src/global_styles/openproject/_mixins.sass b/frontend/src/global_styles/openproject/_mixins.sass
index 1a507392e8f9..032237b35fc9 100644
--- a/frontend/src/global_styles/openproject/_mixins.sass
+++ b/frontend/src/global_styles/openproject/_mixins.sass
@@ -388,3 +388,9 @@ $scrollbar-size: 10px
@media (hover: hover)
&:hover
border-color: var(--borderColor-default) !important
+
+@mixin line-clamp($value)
+ display: -webkit-box
+ -webkit-line-clamp: $value
+ -webkit-box-orient: vertical
+ overflow: hidden
diff --git a/lib/api/v3/projects/create_form_api.rb b/lib/api/v3/projects/create_form_api.rb
index 168c77225c93..fe8b6d839db3 100644
--- a/lib/api/v3/projects/create_form_api.rb
+++ b/lib/api/v3/projects/create_form_api.rb
@@ -39,8 +39,7 @@ class CreateFormAPI < ::API::OpenProjectAPI
post &::API::V3::Utilities::Endpoints::CreateForm.new(model: Project,
params_modifier: ->(attributes) {
- attributes[:workspace_type] = Project.workspace_types[:project]
- attributes
+ attributes.merge!(workspace_type: "project")
})
.mount
end
diff --git a/lib/api/v3/projects/projects_api.rb b/lib/api/v3/projects/projects_api.rb
index f30268b0946f..e3ecc90d1543 100644
--- a/lib/api/v3/projects/projects_api.rb
+++ b/lib/api/v3/projects/projects_api.rb
@@ -42,8 +42,7 @@ class ProjectsAPI < ::API::OpenProjectAPI
post &::API::V3::Utilities::Endpoints::Create.new(model: Project,
params_modifier: ->(attributes) {
- attributes[:workspace_type] = Project.workspace_types[:project]
- attributes
+ attributes.merge!(workspace_type: "project")
})
.mount
diff --git a/lib/primer/open_project/forms/_index.sass b/lib/primer/open_project/forms/_index.sass
new file mode 100644
index 000000000000..4eee9f5aee3a
--- /dev/null
+++ b/lib/primer/open_project/forms/_index.sass
@@ -0,0 +1 @@
+@import "advanced_form_group"
diff --git a/lib/primer/open_project/forms/advanced_check_box.html.erb b/lib/primer/open_project/forms/advanced_check_box.html.erb
new file mode 100644
index 000000000000..6fd082f30b74
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_check_box.html.erb
@@ -0,0 +1,24 @@
+<%= content_tag(:div, class: "FormControl-advanced-checkbox-wrap", hidden: @input.hidden?) do %>
+ <%= builder.label(@input.name, **@input.label_arguments) do %>
+
+ <%= builder.check_box(@input.name, @input.input_arguments, checked_value, unchecked_value) %>
+
+
<%= @input.label %>
+ <% if @input.form_control? %>
+
+ <%= render(Caption.new(input: @input)) %>
+
+ <% end %>
+
+ <% if @input.icon.present? %>
+ <%=
+ inline_svg_tag(
+ @input.icon,
+ class: "FormControl-advanced-checkbox-icon",
+ aria_hidden: true
+ )
+ %>
+ <% end %>
+
+ <% end %>
+<% end %>
diff --git a/lib/primer/open_project/forms/advanced_check_box.rb b/lib/primer/open_project/forms/advanced_check_box.rb
new file mode 100644
index 000000000000..6deea460effe
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_check_box.rb
@@ -0,0 +1,40 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ # :nodoc:
+ class AdvancedCheckBox < Primer::Forms::CheckBox
+ include InlineSvg::ActionView::Helpers
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/advanced_check_box_group.html.erb b/lib/primer/open_project/forms/advanced_check_box_group.html.erb
new file mode 100644
index 000000000000..5319b650faf8
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_check_box_group.html.erb
@@ -0,0 +1,22 @@
+
diff --git a/lib/primer/open_project/forms/advanced_check_box_group.rb b/lib/primer/open_project/forms/advanced_check_box_group.rb
new file mode 100644
index 000000000000..367f5e2e348e
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_check_box_group.rb
@@ -0,0 +1,39 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ # :nodoc:
+ class AdvancedCheckBoxGroup < Primer::Forms::CheckBoxGroup
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/advanced_form_group.sass b/lib/primer/open_project/forms/advanced_form_group.sass
new file mode 100644
index 000000000000..2b9236d6b24a
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_form_group.sass
@@ -0,0 +1,50 @@
+.FormControl-advanced-check-group-wrap,
+.FormControl-advanced-radio-group-wrap
+ & fieldset
+ padding: 0
+ margin: 0
+ border: 0
+
+.FormControl-advanced-check-group-list,
+.FormControl-advanced-radio-group-list
+ display: grid
+ grid-template-columns: repeat(2, 1fr)
+ gap: var(--base-size-12) var(--base-size-16)
+
+ @media screen and (max-width: $breakpoint-sm)
+ grid-template-columns: 1fr
+
+.FormControl-advanced-checkbox-wrap,
+.FormControl-advanced-radio-wrap
+ display: flex
+ background-color: var(--bgColor-inset)
+ border: var(--borderWidth-thin, 1px) solid var(--borderColor-default)
+ border-radius: var(--borderRadius-medium)
+
+ &:has(input:hover)
+ background-color: var(--bgColor-accent-muted)
+ border-color: var(--borderColor-accent-muted)
+
+ &:has(input:checked)
+ background-color: var(--bgColor-accent-muted)
+ border-color: var(--control-checked-borderColor-rest)
+
+ & .FormControl-label
+ display: block
+ flex: 1
+ cursor: pointer
+
+.FormControl-advanced-checkbox-content,
+.FormControl-advanced-radio-content
+ display: grid
+ grid-template-columns: min-content auto min-content
+ gap: var(--base-size-8)
+ padding: var(--base-size-12)
+
+.FormControl-advanced-checkbox-label-text,
+.FormControl-advanced-radio-label-text
+ color: var(--fgColor-accent)
+
+.FormControl-advanced-checkbox-icon,
+.FormControl-advanced-radio-icon
+ fill: var(--fgColor-accent)
diff --git a/lib/primer/open_project/forms/advanced_radio_button.html.erb b/lib/primer/open_project/forms/advanced_radio_button.html.erb
new file mode 100644
index 000000000000..2d82b6c81a8e
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_radio_button.html.erb
@@ -0,0 +1,24 @@
+<%= content_tag(:div, class: "FormControl-advanced-radio-wrap", hidden: @input.hidden?) do %>
+ <%= builder.label(@input.name, value: @input.value, **@input.label_arguments) do %>
+
+ <%= builder.radio_button(@input.name, @input.value, **@input.input_arguments) %>
+
+
<%= @input.label %>
+ <% if @input.form_control? %>
+
+ <%= render(Caption.new(input: @input)) %>
+
+ <% end %>
+
+ <% if @input.icon.present? %>
+ <%=
+ inline_svg_tag(
+ @input.icon,
+ class: "FormControl-advanced-radio-icon",
+ aria_hidden: true
+ )
+ %>
+ <% end %>
+
+ <% end %>
+<% end %>
diff --git a/lib/primer/open_project/forms/advanced_radio_button.rb b/lib/primer/open_project/forms/advanced_radio_button.rb
new file mode 100644
index 000000000000..c980970410cb
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_radio_button.rb
@@ -0,0 +1,40 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ # :nodoc:
+ class AdvancedRadioButton < Primer::Forms::RadioButton
+ include InlineSvg::ActionView::Helpers
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/advanced_radio_button_group.html.erb b/lib/primer/open_project/forms/advanced_radio_button_group.html.erb
new file mode 100644
index 000000000000..b8b563b623f5
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_radio_button_group.html.erb
@@ -0,0 +1,22 @@
+
diff --git a/lib/primer/open_project/forms/advanced_radio_button_group.rb b/lib/primer/open_project/forms/advanced_radio_button_group.rb
new file mode 100644
index 000000000000..f4f76b8f067a
--- /dev/null
+++ b/lib/primer/open_project/forms/advanced_radio_button_group.rb
@@ -0,0 +1,39 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ # :nodoc:
+ class AdvancedRadioButtonGroup < Primer::Forms::RadioButtonGroup
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/dsl/advanced_check_box_group_input.rb b/lib/primer/open_project/forms/dsl/advanced_check_box_group_input.rb
new file mode 100644
index 000000000000..3a9d6eeaafa7
--- /dev/null
+++ b/lib/primer/open_project/forms/dsl/advanced_check_box_group_input.rb
@@ -0,0 +1,87 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ module Dsl
+ # :nodoc:
+ class AdvancedCheckBoxGroupInput < Primer::Forms::Dsl::Input
+ attr_reader :name, :label, :check_boxes
+
+ def initialize(name: nil, label: nil, **system_arguments)
+ @name = name
+ @label = label
+ @check_boxes = []
+
+ super(**system_arguments)
+
+ yield(self) if block_given?
+ end
+
+ def to_component
+ AdvancedCheckBoxGroup.new(input: self)
+ end
+
+ def type
+ :check_box_group
+ end
+
+ def focusable?
+ true
+ end
+
+ def autofocus!
+ @check_boxes.first&.autofocus!
+ end
+
+ def check_box(**system_arguments, &)
+ args = {
+ name: @name,
+ builder: @builder,
+ form: @form,
+ scheme: scheme,
+ disabled: disabled?,
+ **system_arguments
+ }
+
+ @check_boxes << AdvancedCheckBoxInput.new(**args, &)
+ end
+
+ private
+
+ def scheme
+ @name ? :array : :boolean
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/dsl/advanced_check_box_input.rb b/lib/primer/open_project/forms/dsl/advanced_check_box_input.rb
new file mode 100644
index 000000000000..50e106b3a966
--- /dev/null
+++ b/lib/primer/open_project/forms/dsl/advanced_check_box_input.rb
@@ -0,0 +1,87 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ module Dsl
+ # :nodoc:
+ class AdvancedCheckBoxInput < Primer::Forms::Dsl::Input
+ DEFAULT_SCHEME = :boolean
+ SCHEMES = [DEFAULT_SCHEME, :array].freeze
+
+ attr_reader :name, :label, :value, :unchecked_value, :scheme, :icon
+
+ def initialize(name:, label:, value: nil, unchecked_value: nil, scheme: DEFAULT_SCHEME, icon: nil, **system_arguments)
+ raise ArgumentError, "Check box scheme must be one of #{SCHEMES.join(', ')}" unless SCHEMES.include?(scheme)
+
+ raise ArgumentError, "Check box needs an explicit value if scheme is array" if scheme == :array && value.nil?
+
+ @name = name
+ @label = label
+ @value = value
+ @unchecked_value = unchecked_value
+ @scheme = scheme
+ @icon = icon
+
+ super(**system_arguments)
+
+ yield(self) if block_given?
+ end
+
+ # check boxes cannot be invalid, as both checked and unchecked are valid states
+ # :nocov:
+ def valid?
+ true
+ end
+ # :nocov:
+
+ def to_component
+ AdvancedCheckBox.new(input: self)
+ end
+
+ def type
+ :check_box
+ end
+
+ def supports_validation?
+ false
+ end
+
+ def values_disambiguate_template_names?
+ # Check boxes submitted as an array all have the same name, so we return true here
+ # to ensure different caption templates can be attached to individual check box inputs.
+ @scheme == :array
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/dsl/advanced_radio_button_group_input.rb b/lib/primer/open_project/forms/dsl/advanced_radio_button_group_input.rb
new file mode 100644
index 000000000000..66f6a5473267
--- /dev/null
+++ b/lib/primer/open_project/forms/dsl/advanced_radio_button_group_input.rb
@@ -0,0 +1,75 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ module Dsl
+ # :nodoc:
+ class AdvancedRadioButtonGroupInput < Primer::Forms::Dsl::Input
+ attr_reader :name, :label, :radio_buttons
+
+ def initialize(name:, label: nil, **system_arguments)
+ @name = name
+ @label = label
+ @radio_buttons = []
+
+ super(**system_arguments)
+
+ yield(self) if block_given?
+ end
+
+ def to_component
+ AdvancedRadioButtonGroup.new(input: self)
+ end
+
+ def type
+ :radio_button_group
+ end
+
+ def autofocus!
+ @radio_buttons.first&.autofocus!
+ end
+
+ def focusable?
+ true
+ end
+
+ def radio_button(**system_arguments, &)
+ @radio_buttons << AdvancedRadioButtonInput.new(
+ builder: @builder, form: @form, name: @name, disabled: disabled?,
+ **system_arguments, &
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/dsl/advanced_radio_button_input.rb b/lib/primer/open_project/forms/dsl/advanced_radio_button_input.rb
new file mode 100644
index 000000000000..766826e2149b
--- /dev/null
+++ b/lib/primer/open_project/forms/dsl/advanced_radio_button_input.rb
@@ -0,0 +1,78 @@
+# 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 Primer
+ module OpenProject
+ module Forms
+ module Dsl
+ # :nodoc:
+ class AdvancedRadioButtonInput < Primer::Forms::Dsl::Input
+ attr_reader :name, :value, :label, :icon
+
+ def initialize(name:, value:, label:, icon: nil, **system_arguments)
+ @name = name
+ @value = value
+ @label = label
+ @icon = icon
+
+ super(**system_arguments)
+
+ yield(self) if block_given?
+ end
+
+ # radio buttons cannot be invalid, as both selected and unselected are valid states
+ # :nocov:
+ def valid?
+ true
+ end
+ # :nocov:
+
+ def to_component
+ AdvancedRadioButton.new(input: self)
+ end
+
+ # :nocov:
+ def type
+ :radio_button
+ end
+ # :nocov:
+
+ def supports_validation?
+ false
+ end
+
+ def values_disambiguate_template_names?
+ true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/primer/open_project/forms/dsl/input_methods.rb b/lib/primer/open_project/forms/dsl/input_methods.rb
index 7ca247fafb94..7c217c9e2c70 100644
--- a/lib/primer/open_project/forms/dsl/input_methods.rb
+++ b/lib/primer/open_project/forms/dsl/input_methods.rb
@@ -21,6 +21,14 @@ def check_box_group(**, &)
super(**decorate_options(**), &)
end
+ def advanced_radio_button_group(**, &)
+ add_input AdvancedRadioButtonGroupInput.new(builder:, form:, **decorate_options(**), &)
+ end
+
+ def advanced_check_box_group(**, &)
+ add_input AdvancedCheckBoxGroupInput.new(builder:, form:, **decorate_options(**), &)
+ end
+
def autocompleter(**, &)
add_input AutocompleterInput.new(builder:, form:, **decorate_options(**), &)
end
diff --git a/modules/grids/app/components/grids/widgets/subitems.html.erb b/modules/grids/app/components/grids/widgets/subitems.html.erb
index 711cd7d11f91..d830bf56ba35 100644
--- a/modules/grids/app/components/grids/widgets/subitems.html.erb
+++ b/modules/grids/app/components/grids/widgets/subitems.html.erb
@@ -41,7 +41,7 @@ See COPYRIGHT and LICENSE files for more details.
row.with_column do
render(Primer::Beta::Text.new(color: :subtle)) do
concat render Primer::Beta::Octicon.new(
- icon: helpers.workspace_icon(child),
+ icon: helpers.workspace_icon(child.workspace_type),
mr: 1,
color: :subtle,
"aria-label": child.workspace_label
diff --git a/spec/components/projects/template_select_component_spec.rb b/spec/components/projects/template_select_component_spec.rb
index 2344f7051a65..ae78ff2f3548 100644
--- a/spec/components/projects/template_select_component_spec.rb
+++ b/spec/components/projects/template_select_component_spec.rb
@@ -31,26 +31,83 @@
require "rails_helper"
RSpec.describe Projects::TemplateSelectComponent, type: :component do
+ def render_component(...)
+ render_inline(described_class.new(...))
+ end
+
+ let(:project) { Project.new }
let(:template) { build_stubbed(:template_project) }
+ let(:current_user) { build_stubbed(:user) }
- def render_component(**params)
- render_inline(described_class.new(template:, **params))
- page
- end
+ subject(:rendered_component) { render_component(project:, template:, current_user:) }
it "renders form" do
- expect(render_component).to have_css "form"
+ expect(rendered_component).to have_element :form, method: "get"
+ end
+
+ describe "action" do
+ let(:project) { Project.new(workspace_type:) }
+
+ context "when workspace type is not set" do
+ let(:workspace_type) { nil }
+
+ it "sets action to create project" do
+ expect(rendered_component).to have_element :form, method: "get" do |form|
+ expect(form["action"]).to eq "/projects/new"
+ end
+ end
+ end
+
+ context "when workspace type set to unknown value" do
+ let(:workspace_type) { :unknown }
+
+ it "sets action to create project" do
+ expect(rendered_component).to have_element :form, method: "get" do |form|
+ expect(form["action"]).to eq "/projects/new"
+ end
+ end
+ end
+
+ context "when workspace type is set to project" do
+ let(:workspace_type) { :project }
+
+ it "sets action to create project" do
+ expect(rendered_component).to have_element :form, method: "get" do |form|
+ expect(form["action"]).to eq "/projects/new"
+ end
+ end
+ end
+
+ context "when workspace type is set to program" do
+ let(:workspace_type) { :program }
+
+ it "sets action to create project" do
+ expect(rendered_component).to have_element :form, method: "get" do |form|
+ expect(form["action"]).to eq "/programs/new"
+ end
+ end
+ end
+
+ context "when workspace type is set to portfolio" do
+ let(:workspace_type) { :portfolio }
+
+ it "sets action to create project" do
+ expect(rendered_component).to have_element :form, method: "get" do |form|
+ expect(form["action"]).to eq "/portfolios/new"
+ end
+ end
+ end
end
- it "renders project autocompleter" do
- expect(render_component).to have_element "opce-project-autocompleter", "data-input-name": "\"template_id\"" do |element|
- expect(element["data-input-value"]).to eq template.id.to_s
+ it "registers Stimulus controller" do
+ expect(rendered_component).to have_element :form do |form|
+ expect(form["data-controller"]).to include "auto-submit"
end
end
it "connects Stimulus controller actions" do
- expect(render_component).to have_element "opce-project-autocompleter", "data-input-name": "\"template_id\"" do |element|
- expect(element["data-action"]).to include "change->auto-submit#submit"
+ expect(rendered_component).to have_selector :fieldset, "Use template" do |fieldset|
+ expect(fieldset["data-action"]).to include "change->auto-submit#submit"
end
end
end
diff --git a/spec/features/projects/template_spec.rb b/spec/features/projects/template_spec.rb
index 0cbfad433296..10e416c63d1d 100644
--- a/spec/features/projects/template_spec.rb
+++ b/spec/features/projects/template_spec.rb
@@ -85,8 +85,6 @@
create(:user, member_with_roles: { template => role })
end
- let(:template_field) { FormFields::SelectFormField.new :use_template }
-
current_user do
create(:user,
member_with_roles: { template => role, other_project => role },
@@ -100,7 +98,7 @@
expect(page).to have_no_selector :fieldset, "Copy options"
- template_field.select_option "My template"
+ choose "My template", fieldset: "Use 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.
@@ -110,8 +108,6 @@
# expect(page).to have_field "Name", with: "Foo bar"
fill_in "Name", with: "Foo bar"
- template_field.expect_selected "My template"
-
expect(page).to have_unchecked_field fieldset: "Notifications"
# And allows to deselect copying the members.
diff --git a/spec/forms/projects/template_select_form_spec.rb b/spec/forms/projects/template_select_form_spec.rb
new file mode 100644
index 000000000000..bd78ac63b171
--- /dev/null
+++ b/spec/forms/projects/template_select_form_spec.rb
@@ -0,0 +1,116 @@
+# 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::TemplateSelectForm, type: :forms do
+ include_context "with rendered form"
+
+ let(:model) { create(:project) }
+ let(:params) { { template_id:, parent_id:, current_user:, workspace_type: model.workspace_type } }
+ let(:current_user) { create(:admin) }
+
+ let(:template_id) { nil }
+ let(:parent_id) { nil }
+
+ subject(:rendered_form) do
+ render_form
+ page
+ end
+
+ context "with no templates" do
+ %i[project portfolio program].each do |workspace_type|
+ context "when workspace_type is #{workspace_type}" do
+ let(:model) { create(:project, workspace_type:) }
+
+ it "renders Blank #{workspace_type.to_s.capitalize} radio button only" do
+ expect(rendered_form).to have_field type: :radio, count: 1, fieldset: "Use template"
+ expect(rendered_form).to have_field "Blank #{workspace_type}",
+ type: :radio,
+ accessible_description: /^Start from scratch.*#{workspace_type}/
+ end
+ end
+ end
+ end
+
+ context "with templates" do
+ let!(:templates) do
+ [
+ create(:template_project, name: "Agile", description: "**Great for beginners.**"),
+ create(:template_project, name: "SAF€", description: nil),
+ create(:template_project, name: "PRINCE", description: "## His Majesty's Choice.")
+ ]
+ end
+
+ it "renders radio buttons for each template in addition to Blank Project", :aggregate_failures do
+ expect(rendered_form).to have_field type: :radio, count: 4, fieldset: "Use template"
+ expect(rendered_form).to have_field "Blank project",
+ type: :radio,
+ accessible_description: /^Start from scratch.*project/
+ expect(rendered_form).to have_field "Agile",
+ type: :radio,
+ accessible_description: /^Great for beginners\.$/
+ expect(rendered_form).to have_field "SAF€",
+ type: :radio,
+ accessible_description: /^No description provided\.$/
+ expect(rendered_form).to have_field "PRINCE",
+ type: :radio,
+ accessible_description: /^His Majesty's Choice\.$/
+ end
+
+ context "when template_id is nil" do
+ it "renders checked radio button for Blank Project" do
+ expect(rendered_form).to have_checked_field "Blank project", type: :radio
+ end
+ end
+
+ context "when template_id is not nil" do
+ let(:template_id) { templates.last.id }
+
+ it "renders checked radio button for given template" do
+ expect(rendered_form).to have_checked_field "PRINCE", type: :radio
+ end
+ end
+ end
+
+ context "when parent_id is nil" do
+ it "does not render hidden parent_id field" do
+ expect(rendered_form).to have_no_field "parent_id", type: :hidden
+ end
+ end
+
+ context "when parent_id is not nil" do
+ let(:parent_id) { 42 }
+
+ it "renders hidden parent_id field" do
+ expect(rendered_form).to have_field "parent_id", type: :hidden, with: 42
+ end
+ end
+end
diff --git a/spec/lib/primer/open_project/forms/advanced_check_box_group_spec.rb b/spec/lib/primer/open_project/forms/advanced_check_box_group_spec.rb
new file mode 100644
index 000000000000..316f458f0f51
--- /dev/null
+++ b/spec/lib/primer/open_project/forms/advanced_check_box_group_spec.rb
@@ -0,0 +1,142 @@
+# 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 Primer::OpenProject::Forms::AdvancedCheckBoxGroup, type: :forms do
+ include ViewComponent::TestHelpers
+
+ describe "rendering" do
+ let(:params) { {} }
+ let(:model) { build_stubbed(:comment) }
+
+ def render_form
+ render_in_view_context(model, params) do |model, params|
+ primer_form_with(url: "/foo", model:) do |f|
+ render_inline_form(f) do |check_form|
+ check_form.advanced_check_box_group(
+ name: :ultimate_answer,
+ label: "Ultimate answer",
+ **params
+ ) do |group|
+ group.check_box(
+ value: "one",
+ label: "One",
+ caption: "Pick me",
+ icon: "icon_logo.svg"
+ )
+ group.check_box(
+ value: "two",
+ label: "Two",
+ caption: "Don't pick me",
+ icon: "icon_logo.svg"
+ )
+ group.check_box(
+ value: "three",
+ label: "Three",
+ icon: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ subject(:rendered_form) do
+ render_form
+ page
+ end
+
+ it "renders the fieldset" do
+ expect(rendered_form).to have_selector :fieldset, "Ultimate answer"
+ end
+
+ it "renders the checkboxes", :aggregate_failures do
+ expect(rendered_form).to have_field "One", type: :checkbox, fieldset: "Ultimate answer"
+ expect(rendered_form).to have_field "Two", type: :checkbox, fieldset: "Ultimate answer"
+ expect(rendered_form).to have_field "Three", type: :checkbox, fieldset: "Ultimate answer"
+ end
+
+ it "renders icons" do
+ expect(rendered_form).to have_element :svg, count: 2, aria: { hidden: true }
+ end
+
+ it "renders captions", :aggregate_failures do
+ expect(rendered_form).to have_css ".FormControl-caption", count: 2
+ expect(rendered_form).to have_css ".FormControl-caption", text: "Pick me"
+ expect(rendered_form).to have_css ".FormControl-caption", text: "Don't pick me"
+ end
+ end
+
+ describe "standard checkbox group compatibility" do
+ specify "hidden checkbox group", :aggregate_failures do
+ render_in_view_context do
+ primer_form_with(url: "/foo") do |f|
+ render_inline_form(f) do |check_form|
+ check_form.advanced_check_box_group(label: "Foobar", hidden: true) do |check_group|
+ check_group.check_box(name: :foo, label: "Foo")
+ end
+ end
+ end
+ end
+
+ expect(page).to have_selector :fieldset, visible: :hidden
+ expect(page).to have_css ".FormControl-advanced-checkbox-wrap", visible: :hidden
+ end
+
+ specify "disabled checkbox group disables constituent checkboxes" do
+ render_in_view_context do
+ primer_form_with(url: "/foo") do |f|
+ render_inline_form(f) do |check_form|
+ check_form.advanced_check_box_group(label: "Foobar", disabled: true) do |check_group|
+ check_group.check_box(name: :foo, label: "Foo")
+ end
+ end
+ end
+ end
+
+ expect(page).to have_css ".FormControl-advanced-checkbox-wrap input[disabled]"
+ end
+
+ specify "checkbox can be individually disabled in group" do
+ render_in_view_context do
+ primer_form_with(url: "/foo") do |f|
+ render_inline_form(f) do |check_form|
+ check_form.advanced_check_box_group(label: "Foobar") do |check_group|
+ check_group.check_box(name: :foo, label: "Foo", disabled: true)
+ end
+ end
+ end
+ end
+
+ expect(page).to have_css ".FormControl-advanced-checkbox-wrap input[disabled]"
+ end
+ end
+end
diff --git a/spec/lib/primer/open_project/forms/advanced_radio_button_group_spec.rb b/spec/lib/primer/open_project/forms/advanced_radio_button_group_spec.rb
new file mode 100644
index 000000000000..0702134160ac
--- /dev/null
+++ b/spec/lib/primer/open_project/forms/advanced_radio_button_group_spec.rb
@@ -0,0 +1,142 @@
+# 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 Primer::OpenProject::Forms::AdvancedRadioButtonGroup, type: :forms do
+ include ViewComponent::TestHelpers
+
+ describe "rendering" do
+ let(:params) { {} }
+ let(:model) { build_stubbed(:comment) }
+
+ def render_form
+ render_in_view_context(model, params) do |model, params|
+ primer_form_with(url: "/foo", model:) do |f|
+ render_inline_form(f) do |radio_form|
+ radio_form.advanced_radio_button_group(
+ name: :ultimate_answer,
+ label: "Ultimate answer",
+ **params
+ ) do |group|
+ group.radio_button(
+ value: "one",
+ label: "One",
+ caption: "Pick me",
+ icon: "icon_logo.svg"
+ )
+ group.radio_button(
+ value: "two",
+ label: "Two",
+ caption: "Don't pick me",
+ icon: "icon_logo.svg"
+ )
+ group.radio_button(
+ value: "three",
+ label: "Three",
+ icon: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ subject(:rendered_form) do
+ render_form
+ page
+ end
+
+ it "renders the fieldset" do
+ expect(rendered_form).to have_selector :fieldset, "Ultimate answer", role: "radiogroup"
+ end
+
+ it "renders the radio buttons", :aggregate_failures do
+ expect(rendered_form).to have_field "One", type: :radio, fieldset: "Ultimate answer"
+ expect(rendered_form).to have_field "Two", type: :radio, fieldset: "Ultimate answer"
+ expect(rendered_form).to have_field "Three", type: :radio, fieldset: "Ultimate answer"
+ end
+
+ it "renders icons" do
+ expect(rendered_form).to have_element :svg, count: 2, aria: { hidden: true }
+ end
+
+ it "renders captions", :aggregate_failures do
+ expect(rendered_form).to have_css ".FormControl-caption", count: 2
+ expect(rendered_form).to have_css ".FormControl-caption", text: "Pick me"
+ expect(rendered_form).to have_css ".FormControl-caption", text: "Don't pick me"
+ end
+ end
+
+ describe "standard radio button group compatibility" do
+ specify "hidden radio button group", :aggregate_failures do
+ render_in_view_context do
+ primer_form_with(url: "/foo") do |f|
+ render_inline_form(f) do |radio_form|
+ radio_form.advanced_radio_button_group(name: :foobar, label: "Foobar", hidden: true) do |radio_group|
+ radio_group.radio_button(value: "Foo", label: "Foo")
+ end
+ end
+ end
+ end
+
+ expect(page).to have_selector :fieldset, visible: :hidden
+ expect(page).to have_css ".FormControl-advanced-radio-wrap", visible: :hidden
+ end
+
+ specify "disabled radio group disables constituent radios" do
+ render_in_view_context do
+ primer_form_with(url: "/foo") do |f|
+ render_inline_form(f) do |radio_form|
+ radio_form.advanced_radio_button_group(name: :foobar, label: "Foobar", disabled: true) do |radio_group|
+ radio_group.radio_button(value: "Foo", label: "Foo")
+ end
+ end
+ end
+ end
+
+ expect(page).to have_css ".FormControl-advanced-radio-wrap input[disabled]"
+ end
+
+ specify "radio can be individually disabled in group" do
+ render_in_view_context do
+ primer_form_with(url: "/foo") do |f|
+ render_inline_form(f) do |radio_form|
+ radio_form.advanced_radio_button_group(name: :foobar, label: "Foobar") do |radio_group|
+ radio_group.radio_button(value: "Foo", label: "Foo", disabled: true)
+ end
+ end
+ end
+ end
+
+ expect(page).to have_css ".FormControl-advanced-radio-wrap input[disabled]"
+ end
+ end
+end
diff --git a/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb b/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb
index 1b63d4e8a9f2..c12ed3c3fd39 100644
--- a/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb
+++ b/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb
@@ -113,6 +113,20 @@
include_examples "input class", Primer::Forms::Dsl::CheckBoxGroupInput
it_behaves_like "supporting help texts"
end
+
+ describe "#advanced_radio_button_group" do
+ let(:field_group) { form_dsl.advanced_radio_button_group(name:, label:, **options) }
+
+ include_examples "input class", Primer::OpenProject::Forms::Dsl::AdvancedRadioButtonGroupInput
+ it_behaves_like "supporting help texts"
+ end
+
+ describe "#advanced_check_box_group" do
+ let(:field_group) { form_dsl.advanced_check_box_group(name:, label:, **options) }
+
+ include_examples "input class", Primer::OpenProject::Forms::Dsl::AdvancedCheckBoxGroupInput
+ it_behaves_like "supporting help texts"
+ end
end
describe "#separator" do
diff --git a/spec/support/forms/rendered_form.rb b/spec/support/forms/rendered_form.rb
index 613173475208..a31d3ff9df3d 100644
--- a/spec/support/forms/rendered_form.rb
+++ b/spec/support/forms/rendered_form.rb
@@ -30,10 +30,12 @@
RSpec.shared_context "with rendered form" do
include ViewComponent::TestHelpers
+ let(:params) { {} }
+
def render_form
- render_in_view_context(model, described_class) do |model, described_class|
+ render_in_view_context(model, described_class, params) do |model, described_class, params|
primer_form_with(url: "/foo", model:) do |f|
- render(described_class.new(f))
+ render(described_class.new(f, **params))
end
end
end