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 @@ +
+ <%= content_tag(:fieldset, **@input.input_arguments) do %> + <% if @input.label %> + <%= content_tag(:legend, **@input.label_arguments) do %> + <%= @input.label %> + <% end %> + <% end %> +
+ <%= render(Caption.new(input: @input)) %> +
+ <%= render(SpacingWrapper.new) do %> +
+ <% @input.check_boxes.each do |check_box| %> + <%= render(check_box.to_component) %> + <% end %> +
+ <% end %> + <% end %> +
+ <%= render(ValidationMessage.new(input: @input)) %> +
+
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 @@ +
+ <%= content_tag(:fieldset, role: "radiogroup", **@input.input_arguments) do %> + <% if @input.label %> + <%= content_tag(:legend, **@input.label_arguments) do %> + <%= @input.label %> + <% end %> + <% end %> +
+ <%= render(Caption.new(input: @input)) %> +
+ <%= render(SpacingWrapper.new) do %> +
+ <% @input.radio_buttons.each do |radio_button| %> + <%= render(radio_button.to_component) %> + <% end %> +
+ <% end %> + <% end %> +
+ <%= render(ValidationMessage.new(input: @input)) %> +
+
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